// ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ // // Copyright (c) 2001-2003, OpenBeOS // // This software is part of the OpenBeOS distribution and is covered // by the OpenBeOS license. // // // File: printf.c // Author: Daniel Reinhold (danielre@users.sf.net) // Description: command line version of printf // // Comments: // [6 Oct 2002] // This utility was written using the following document for reference: // // http://www.opengroup.org/onlinepubs/007908799/xcu/printf.html // // (which is an online Unix man page) and by testing the output against // the BeOS R5 program '/bin/printf'. There are a few deviations in the // output between the two, but they are so minor and technical that I // don't think any normal human being would care ;-). // // ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ #include #include #include #include #include #include void do_printf (int, char **); int esc_string_len(char *, bool *); char escaped (char, char **); int get_decimal (char **); char get_next_specifier(char *, int); void get_spec_fields(char *, bool *, int *, int *); bool parse_hex (char **, int, int, char *); bool parse_octal (char **, int, int, char *); void print_escaped_string (char *, char *); void print_next_arg (void); typedef union { int n; double d; } number; number string_to_number(char *, bool); inline int string_to_decimal(char *); inline double string_to_real(char *); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // globals // the variables below don't really have to be global, // but it sure simplifies the code this way (trust me!) static char *Format; // format string static char **Argv; // the list of args to be printed static int Argc; // arg count // a kludgy (but useful) value to encode a "\c" escape static const char SLASHC = 1; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // top-level int main(int argc, char *argv[]) { if (argc < 2) printf("Usage: printf format [arguments]\n"); else do_printf(argc, argv); return 0; } void do_printf(int argc, char *argv[]) { char c; char e; // the actual print arguments start at argv[2] Argv = argv + 2; Argc = argc - 2; for (;;) { Format = argv[1]; // inner loop: interpret the format string while ((c = *Format++) != 0) { if (c == '%') { if (*Format == '%') ++Format, putchar('%'); else print_next_arg(); } else { e = escaped(c, &Format); putchar(e); } } if (Argc == 0) return; // for any args remaining, just cycle thru again // re-using the previous format string... } } void print_next_arg() { // prints the next argument using the current specifier. // an empty string ("") is used for the output arg // when there are no more arguments remaining char fmt[64]; // format specifier for a single arg char fc; // format character char *arg = ""; // next arg to be printed if (Argc > 0) arg = *Argv++, Argc--; fc = get_next_specifier(fmt, sizeof fmt); switch (fc) { case 'c': printf(fmt, arg[0]); break; case 's': printf(fmt, arg); break; case 'b': // fmt+1 ==> skips over the unneeded '%' char print_escaped_string(fmt+1, arg); break; case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': case 'p': printf(fmt, string_to_decimal(arg)); break; case 'f': case 'g': case 'G': case 'e': case 'E': printf(fmt, string_to_real(arg)); break; default: fprintf(stderr, "printf: `%c': illegal format character\n", escaped(fc, &Format)); exit(1); } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // parsing, character processing... char get_next_specifier(char *spec_buffer, int buflen) { // called whenever a '%' is found in the format string. // extracts the next format specifier from the global format string // (which is advanced) and copies it into the given buffer. // the final format character is returned char *p = spec_buffer; char c; char fc = 0; int len = 1; // will copy in the '%' regardless... *p++ = '%'; while ((c = *Format++) != 0) { *p++ = c; if (++len > buflen) break; if (isalpha(c) || c == '%' || c == '\\') { fc = c; break; } } *p = 0; if (fc == 0) { fprintf(stderr, "printf: `%%': missing format character\n"); exit(1); } return fc; } int esc_string_len(char *s, bool *halt) { // returns the string length that the string would have // if all (of any) embedded escape sequences were converted // e.g. "x\ty\tz\n" ==> 6 // // note: the special '\c' escape char is not counted, // but instead terminates the string and sets the halt flag char c; char e; int len = 0; while ((c = *s++) != 0) { e = escaped(c, &s); if (e == SLASHC) break; ++len; } *halt = (e == SLASHC); return len; } void print_escaped_string(char *fmt, char *s) { // called whenever the "%b" format specifier is found. // prints a string possibly containing escaped chars // (precision field holds the number of chars requested) // // this is the only place where the "\c" escape sequence // is to be interpreted (i.e. the program halted) int pad, lpad, rpad; // count of padding chars bool leftAdjust; // left/right adjust? int fwidth; // specified field width int wanted; // requested number of chars int len; // number of chars to actually print bool halt = false; get_spec_fields(fmt, &leftAdjust, &fwidth, &wanted); // compute the string length and padding len = esc_string_len(s, &halt); if (wanted > 0 && wanted < len) len = wanted; pad = fwidth - len; if (leftAdjust) lpad = 0, rpad = pad; else lpad = pad, rpad = 0; // print the translated chars with padding { char c; char e; while (lpad-- > 0) putchar(' '); // pad on the left while (len-- > 0) { c = *s++; e = escaped(c, &s); putchar(e); } while (rpad-- > 0) putchar(' '); // pad on the right } if (halt) exit(0); } void get_spec_fields(char *specifier, bool *leftAdjust, int *fieldwidth, int *precision) { // parses the given specifier to determine: // * left/right adjustment // * field width // * precision // these values are then returned thru the parameters // note: this routine is only called for the "%b" conversion const char *flags = " 0-+#"; char *s = specifier; *leftAdjust = false; *fieldwidth = 0; *precision = 0; while (strchr(flags, *s)) { if (*s == '-') // only flag we care about here *leftAdjust = true; ++s; } if (isdigit (*s)) { *fieldwidth = get_decimal(&s); if (*s == '.') ++s, *precision = get_decimal(&s); } if (!(isalpha(*s))) { fprintf(stderr, "printf: `%c': illegal format character\n", escaped(*s, &s)); exit(1); } } char escaped(char c, char **src) { // returns the character value for an escape sequence // if no escape is found, the original char is returned // the source pointer is advanced for any extra chars read char *p = *src; // local pointer to scan thru the string char esc; // the converted character bool found = true; // assume that an escape will be found if (c != '\\') return c; switch (*p++) { case 'a': esc = '\a'; break; case 'b': esc = '\b'; break; case 'f': esc = '\f'; break; case 'n': esc = '\n'; break; case 'r': esc = '\r'; break; case 't': esc = '\t'; break; case 'v': esc = '\v'; break; case '\\': esc = '\\'; break; case '"': esc = '"'; break; case '\'': esc = '\''; break; case 'c': esc = SLASHC; break; case '0': // format: "\0ooo" (0 to 3 octal digits) found = parse_octal(&p, 0, 3, &esc); break; case 'x': // format: "\xhhh" (1 to 3 hex digits) found = parse_hex(&p, 1, 3, &esc); break; default: // nope, not an escape sequence found = false; } if (found) { *src = p; // update the source pointer return esc; } return c; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // numeric routines int get_decimal(char **src) { // grabs a decimal integer value from the source string // and returns the full computed value. // if any valid digits were found, the source pointer is advanced. // otherwise, a default value of 0 is returned char *s = *src; char c; int n = 0; while (isdigit(c = *s)) { ++s; n = n * 10 + (c - '0'); } *src = s; return n; } #define isodigit(c) (isdigit(c) && !(c == '8' || c == '9')) bool parse_octal(char **src, int min_digits, int max_digits, char *octval) { // searches for an octal value in the source string. // returns true if valid octal digits were found // // at least 'min_digits' and no more than 'max_digits' // of valid octal characters must be present // // on success, the source pointer is advanced and the // full octal value is returned in the last parameter. // otherwise, the pointer is left unchanged and the // value of the last parameter is undefined. char *s = *src; char c; int n = 0; int i; for (i = 0; i < max_digits; ++i) { c = *s; if (isodigit(c)) { ++s; n = n * 8 + (c - '0'); } else break; } if (i >= min_digits) { *src = s; *octval = (char) n; return true; } return false; } bool parse_hex(char **src, int min_digits, int max_digits, char *hexval) { // searches for a hex value in the source string. // returns true if valid hex digits were found // // at least 'min_digits' and no more than 'max_digits' // of valid hex characters must be present // // on success, the source pointer is advanced and the // full hex value is returned in the last parameter. // otherwise, the pointer is left unchanged and the // value of the last parameter is undefined. char *s = *src; char c; int n = 0; int i; for (i = 0; i < max_digits; ++i) { c = tolower(*s); if (isxdigit(c)) { ++s; n *= 16; if (c > '9') n += (c - 'a' + 10); else n += (c - '0'); } else break; } if (i >= min_digits) { *src = s; *hexval = (char) n; return true; } return false; } inline int string_to_decimal(char *numstr) { return string_to_number(numstr, false).n; } inline double string_to_real(char *numstr) { return string_to_number(numstr, true).d; } number string_to_number(char *numstr, bool isreal) { // converts a numeric string to a number value // (either a decimal integer or a floating point) // // since the code for the two types of conversion is very // nearly the same, this master function takes care of both, // albeit at the price of having to create a union type // to hold the return values // // invalid numeric formats should be caught here and cause // the program to abort with an error message char *end; number num; // check for a character integer format (e.g. 'A ==> 65) if (numstr[0] == '\"' || numstr[0] == '\'') { if (isreal) num.d = (double) numstr[1]; else num.n = (int) numstr[1]; return num; } // attempt to convert with a library function errno = 0; if (isreal) num.d = strtod(numstr, &end); else num.n = (int) strtol(numstr, &end, 10); if (errno) { fprintf(stderr, "printf: %s: %s\n", numstr, strerror(errno)); exit(1); } if (*end) { fprintf(stderr, "printf: %s: illegal number\n", numstr); exit(1); } return num; }