/* printf.c - Format and Print the data. * * Copyright 2014 Sandeep Sharma * Copyright 2014 Kyungwan Han * * 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(); }