blob: 0970c8cd884b7ceb402c13f8d0c0fcb171bfa7e1 [file] [log] [blame]
/* printf.c - Format and Print the data.
*
* Copyright 2014 Sandeep Sharma <sandeep.jack2756@gmail.com>
* Copyright 2014 Kyungwan Han <asura321@gmail.com>
*
* See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html
USE_PRINTF(NEWTOY(printf, "<1", TOYFLAG_USR|TOYFLAG_BIN))
config PRINTF
bool "printf"
default n
help
usage: printf Format [Arguments..]
Format and print ARGUMENT(s) according to FORMAT.
Format is 'C' control output as 'C' printf.
*/
#define FOR_printf
#include "toys.h"
GLOBALS(
char *hv_w;
char *hv_p;
int encountered;
)
// Calculate width and precision from format string
static int get_w_p()
{
char *ptr, *str = *toys.optargs;
errno = 0;
if (*str == '-') str++;
long value = strtol(str, &ptr, 10);
if (errno || (ptr && (*ptr != '\0' || ptr == str)))
perror_msg("Invalid num %s", *toys.optargs);
if (*--str == '-') return (int)(-1 * value);
return value;
}
// Add ll and L to Interger and floating point formats respectively.
static char *get_format(char *f)
{
int len = strlen(f);
char last = f[--len], *post = "";
f[len] = '\0';
if (strchr("diouxX", last)) post = "ll"; // add ll to integer modifier.
else if (strchr("feEgG", last)) post = "L"; // add L to float modifier.
return xmprintf("%s%s%c", f, post, last);
}
// Print arguments with corresponding conversion and width and precision.
static void print(char *fmt, int w, int p, int l)
{
char *ptr = (fmt+l-1), *ep = NULL, *format = NULL;
long long val;
long double dval;
errno = 0;
switch (*ptr) {
case 'd': /*FALL_THROUGH*/
case 'i': /*FALL_THROUGH*/
case 'o': /*FALL_THROUGH*/
case 'u': /*FALL_THROUGH*/
case 'x': /*FALL_THROUGH*/
case 'X':
if (!*toys.optargs) val = 0;
else {
if (**toys.optargs == '\'' || **toys.optargs == '"')
val = *((*toys.optargs) + 1);
else {
val = strtoll(*toys.optargs, &ep, 0);
if (errno || (ep && (*ep != '\0' || ep == *toys.optargs))) {
perror_msg("Invalid num %s", *toys.optargs);
val = 0;
}
}
}
format = get_format(fmt);
TT.hv_w ? (TT.hv_p ? printf(format, w, p, val) : printf(format, w, val))
: (TT.hv_p ? printf(format, p, val) : printf(format, val));
break;
case 'g': /*FALL_THROUGH*/
case 'e': /*FALL_THROUGH*/
case 'E': /*FALL_THROUGH*/
case 'G': /*FALL_THROUGH*/
case 'f':
if (*toys.optargs) {
dval = strtold(*toys.optargs, &ep);
if (errno || (ep && (*ep != '\0' || ep == *toys.optargs))) {
perror_msg("Invalid num %s", *toys.optargs);
dval = 0;
}
} else dval = 0;
format = get_format(fmt);
TT.hv_w ? (TT.hv_p ? printf(format, w, p, dval):printf(format, w, dval))
: (TT.hv_p ? printf(format, p, dval) : printf(format, dval));
break;
case 's':
{
char *str = (*toys.optargs ? *toys.optargs : "");
TT.hv_w ? (TT.hv_p ? printf(fmt,w,p,str): printf(fmt, w, str))
: (TT.hv_p ? printf(fmt, p, str) : printf(fmt, str));
}
break;
case 'c':
printf(fmt, (*toys.optargs ? **toys.optargs : '\0'));
break;
}
if (format) free(format);
}
// Handle the escape sequences.
static int handle_slash(char **esc_val)
{
char *ptr = *esc_val;
int esc_length = 0;
unsigned base = 0, num = 0, result = 0, count = 0;
/*
* Hex escape sequence have only 1 or 2 digits, xHH. Oct escape sequence
* have 1,2 or 3 digits, xHHH. Leading "0" (\0HHH) we are ignoring.
*/
if (*ptr == 'x') {
ptr++;
esc_length++;
base = 16;
} else if (isdigit(*ptr)) base = 8;
while (esc_length < 3 && base) {
num = tolower(*ptr) - '0';
if (num > 10) num += ('0' - 'a' + 10);
if (num >= base) {
if (base == 16) {
esc_length--;
if (!esc_length) {// Invalid hex value eg. /xvd, print as it is /xvd
result = '\\';
ptr--;
}
}
break;
}
esc_length++;
count = result = (count * base) + num;
ptr++;
}
if (base) {
ptr--;
*esc_val = ptr;
return (char)result;
} else {
switch (*ptr) {
case 'n': result = '\n'; break;
case 't': result = '\t'; break;
case 'e': result = (char)27; break;
case 'b': result = '\b'; break;
case 'a': result = '\a'; break;
case 'f': result = '\f'; break;
case 'v': result = '\v'; break;
case 'r': result = '\r'; break;
case '\\': result = '\\'; break;
default :
result = '\\';
ptr--; // Let pointer pointing to / we will increment after returning.
break;
}
}
*esc_val = ptr;
return (char)result;
}
// Handle "%b" option with '\' interpreted.
static void print_esc_str(char *str)
{
for (; *str; str++) {
if (*str == '\\') {
str++;
xputc(handle_slash(&str)); //print corresponding char
} else xputc(*str);
}
}
// Prase the format string and print.
static void parse_print(char *f)
{
char *start, *p, format_specifiers[] = "diouxXfeEgGcs";
int len = 0, width = 0, prec = 0;
while (*f) {
switch (*f) {
case '%':
start = f++;
len++;
if (*f == '%') {
xputc('%');
break;
}
if (*f == 'b') {
if (*toys.optargs) {
print_esc_str(*toys.optargs++);
TT.encountered = 1;
} else print_esc_str("");
break;
}
if (strchr("-+# ", *f)) f++, len++;
if (*f == '*') {
f++, len++;
if (*toys.optargs) {
width = get_w_p();
toys.optargs++;
}
} else {
while (isdigit(*f)) f++, len++;
}
if (*f == '.') {
f++, len++;
if (*f == '*') {
f++, len++;
if (*toys.optargs) {
prec = get_w_p();
toys.optargs++;
}
} else {
while (isdigit(*f)) f++, len++;
}
}
if (!(p = strchr(format_specifiers, *f)))
perror_exit("Missing OR Invalid format specifier");
else {
len++;
TT.hv_p = strstr(start, ".*");
TT.hv_w = strchr(start, '*');
//pitfall: handle diff b/w * and .*
if ((TT.hv_w-1) == TT.hv_p) TT.hv_w = NULL;
memcpy((p = xzalloc(len+1)), start, len);
print(p, width, prec, len);
if (*toys.optargs) toys.optargs++;
free(p);
p = NULL;
}
TT.encountered = 1;
break;
case '\\':
if (f[1]) {
if (*++f == 'c') exit(0); //Got '\c', so no further output
xputc(handle_slash(&f));
} else xputc(*f);
break;
default:
xputc(*f);
break;
}
f++;
len = 0;
}
}
void printf_main(void)
{
char *format = *toys.optargs++;
TT.encountered = 0;
parse_print(format); //printf acc. to format.
//Re-use FORMAT arg as necessary to convert all given ARGS.
while (*toys.optargs && TT.encountered) parse_print(format);
xflush();
}