diff --git a/src/apps/bin/printf.c b/src/apps/bin/printf.c new file mode 100644 index 0000000000..f6a0b256db --- /dev/null +++ b/src/apps/bin/printf.c @@ -0,0 +1,552 @@ +// ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ +// +// Copyright (c) 2001-2002, 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; +} +