diff options
Diffstat (limited to 'libc/stdio1/printf.c')
-rw-r--r-- | libc/stdio1/printf.c | 649 |
1 files changed, 649 insertions, 0 deletions
diff --git a/libc/stdio1/printf.c b/libc/stdio1/printf.c new file mode 100644 index 0000000..f2a39bd --- /dev/null +++ b/libc/stdio1/printf.c @@ -0,0 +1,649 @@ +/* fprintf.c for limited Linux libc + Copyright (C) 1996 Joel N. Weber II <nemo@koa.iolani.honolulu.hi.us> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +/* Thanks Alan for writing the hard routine for me :-) + * Alan said that this works "most of the time". Something tells me I'm making + * it even worse! */ +/* The basic idea here is to make fprintf the core routine. printf obviously + can just call fprintf with stdout followed by all of its arguments. + sprintf() works using the fake file &the_sprintf. It's marked as fully + buffered, so that it will only write(2) when &the_sprintf->bufpos == + &the_sprintf->bufend, which I doubt will happen since &the_sprintf->bufend + = 0. The trick is that sprintf must set &the_sprintf->bufstart = + &the_sprintf->bufpos = its first argument. Not as orthagonal (is that + the right word?) as glibc's facilities for non-files, but this isn't a + library for people who have unlimited RAM budgets. (not like the libc + I use on linux/i586 enjoys an unlimited RAM budget either; I only have + 8 MB + + I'm not sure what the "correct" way to pass the variable arguments + from one function to the next is. Rather than pass the arguments + themselves, I'm passing a pointer to them. However, the following + explaination from Alan is probably a polite way of saying it will not + work on a 386 anyway. + Joel Weber + + [ I've migrated all of this code over to the ELKS stdarg that I wrote. + The accepted way to do it is for the wrapper function to accept a + variable number of arguments, use stdarg to make an argument pointer, + and then pass the argument pointer on to the core function, as I've + done here. This should definitely work on a 386, as the arguments + are still passed in the stack, and stack order is maintained. -Nat ] + */ + +/* + * This is NOT stunningly portable, but works + * for pretty dumb non ANSI compilers and is + * tight. Adjust the sizes to taste. + * + * Illegal format strings will break it. Only the + * following simple subset is supported + * + * %x - hex + * %d - decimal + * %s - string + * %c - char + * + * And the h/l length specifiers for %d/%x + * + * Alan Cox. + */ + +#include <stdarg.h> +#include "stdio.h" + +/* 17 to make sure that there's room for the trailing newline. + I'm not really sure if this is ideal... */ +static char nstring[17]="0123456789ABCDEF"; + +static unsigned char * +__numout(long i, int base) +{ + static unsigned char out[16]; + int n; + int flg = 0; + unsigned long val; + + if (i<0 && base==10) + { + flg = 1; + i = -i; + } + val = i; + + for (n = 0; n < 15; n++) + out[n] = ' '; + out[15] = '\0'; + n = 14; + do{ + out[n] = nstring[val % base]; + n--; + val /= base; + } + while(val); + if(flg) out[n--] = '-'; + return &out[n+1]; +} + +int +fprintf(FILE * stream, __const char * fmt, ...) +{ + va_list ap; + int retval; + va_start(ap, fmt); + retval=internal_fprintf(stream, fmt, ap); + va_end(ap); + return(retval); +} + +int +printf(__const char * fmt, ...) +{ + va_list ap; + int retval; + va_start(ap, fmt); + retval=internal_fprintf(stdout, fmt, ap); + va_end(ap); + return(retval); +} + +/* This is a strange way of doing sprintf, but it should work */ +int sprintf(char * s, __const char * fmt, ...) +{ + static FILE the_sprintf = { + -1, + 0, + 0, + 0, + 0, + _IOFBF, + _MODE_WRITE, + 0, 0, + 0, 0}; + va_list ap; + int retval; + + va_start(ap, fmt); + the_sprintf.bufstart = the_sprintf.bufpos = (unsigned char *) s; + the_sprintf.fc_err = 0; + + retval = internal_fprintf(&the_sprintf, fmt, ap); + /* null-terminate the string */ + putc('\0', &the_sprintf); + + va_end(ap); + return retval; +} + + + + + +/* + * printf.c - This is the more complete fprintf() replacement for libc8086 + * Copyright (C) 1996 Steven Huang <sthuang@hns.com>. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdio.h> +#include <string.h> +#include <ctype.h> + +/* + * This decides if the little test main program gets included + */ +#undef TEST +/* + * This decides if printf() should act like standard. When undefined, + * - prints out "(err)" if a bad format is encountered + * - supports the %b (binary) format + */ +#define STANDARD + +/* + * Shut bcc up about 'const', which doesn't seem to be handled right + * by unproto. + */ +#ifdef __BCC__ +#define const +#endif + +#define BUF_SIZE 128 +#define OUTC(c) { putc(c, stream); n++; } +#define max(a, b) ((a > b) ? a : b) +/* + * if you change the ff, you need to change the order of characters in + * the string 'flagstr' defined in _printf() + */ +#define FLAG_PADZERO (1<<0) +#define FLAG_LEFTJUST (1<<1) +#define FLAG_SIGNED (1<<2) +#define FLAG_ALT (1<<3) +#define FLAG_SPACE (1<<4) + +static char *utoa(unsigned int val, char *buf, int radix) +/* + * Converts an integer to a variable-radix string representation + * + * Note: + * Does not perform sanity checking for 'radix' + * Assumes 'buf' has memory allocated to it + */ +{ + int divisor; + char *p = buf; + const char *digitstr = "0123456789abcdef"; + + for (divisor = 1; val / divisor >= radix; divisor *= radix); + do { + *p++ = digitstr[val / divisor]; + val %= divisor; + divisor /= radix; + } while (divisor >= 1); + *p = '\0'; + return(buf); +} + +static +internal_fprintf(FILE * stream, const char * fmt, char *args) + +/* static int _printf(FILE *stream, const char *fmt, char *args) */ +/* + * The one that does all the work. + * This is a fairly complete implementation of printf(), supporting + * everything EXCEPT: + * - floating point (eEDOUfg formats) + * - pointers (realizes them but doesn't understand how to print them) + * - short and long (h/l) modifiers (dunno what to do about them) + * It's even slightly faster than gcc's printf() on Linux. Can't beat + * HP-UX's printf() though ;) + * + * Supports: + * d, signed integer + * i + * o unsigned octal integer + * u unsigned integer + * x unsigned hex lowercase a-f + * X unsigned hex uppercase A-F + * c character + * s string (character pointer) + * p void pointer (ignores it) + * % % character + * n number of characters output so far + * + * Special feature: (no really, it's not a bug =) ) + * b prints an integer in binary form (i think this might come + * in handy *somewhere*) + * + * # alternate format for octal (leading 0) and hex (0x/0X) + * 0 leading zeroes for d, i, o, u, x, and X + * - left justify, overrides '0' + * ' ' (space) force a blank in front of positive numbers + * + force a sign in front of any number + * + * width.precision, including support for '*' (reads value from + * the parameter list (MUST BE INT)) + * + * h, short/long modifiers, recognized but ignored. + * l + * + * Warning: + * The way varargs is implemented in libc is evil. Don't think + * there's a better way, but misaligned or wrong parameters + * passed to printf() can break a lot of things. I've tried my + * best to handle errors in the format string, though. + * + * Each %something field cannot exceed 'BUF_SIZE' characters, + * which I set to 128 right now. %s fields are not subject to + * this limit. + * + * Note: + * The semicolon -looks- missing in a few places but that's + * because of the macro def of 'OUTC'. did it that way to + * save a few lines of source ;) + * + * Expects a 'char *' as varargs parameter, unlike libc8086's + * printf(), which takes a 'int *' then casts it to a 'char *'. + * Either has to change, but it should be trivial. + * + * This function aborts whenever it scans an illegal format, unlike + * gcc's libc which prints out the illegal format as if it's -not- + * a format string. The 'STANDARD' preprocessor flag controls if + * if just aborts (when defined) or prints out "(err)" (when undefined). + */ +{ + /* + * the "0-+# " and "dcs..." strings are listed in my idea of their + * frequency of use, with the most popular in front. not terribly + * important but strchr() might have an easier time that way. + * if you change the ordering of 'flagstr', make sure you update + * the #define FLAG_* stuff on top of this file too. + */ + char c, *s, *f; + const char *flagstr = "0-+# ", +#ifdef STANDARD + *typestr = "dcsixXuop"; +#else + *typestr = "dcsixXuopb"; +#endif + int n = 0, flags, width, actwidth, prec, bad = 0, neg, i; + static char buf[BUF_SIZE]; + + for (c = *fmt++; c && !bad;) { + if (c != '%') { /* just copy */ + OUTC(c); + c = *fmt++; + } + else { + c = *fmt++; /* chew the % sign */ + flags = width = prec = 0; + /* + * Parse the "0-+# " flags + */ + while ((f = strchr(flagstr, c)) != NULL) { + flags |= 1 << (f - flagstr); + c = *fmt++; + } + /* + * The '*' parameter says fetch width value from varargs + */ + if (c == '*') { + width = *(int *) args; + args += sizeof(int); + if (width < 0) { + width = abs(width); + flags |= FLAG_LEFTJUST; /* set '-' flag */ + } + c = *fmt++; + } + else + /* + * scan and convert the width parameter + */ + if (isdigit(c)) + while (isdigit(c)) { + width *= 10; + width += c - '0'; + c = *fmt++; + } + /* + * a '.' means there may be a precision parameter + */ + if (c == '.') { + c = *fmt++; + /* + * fetch precision value from varargs + */ + if (c == '*') { + prec = *(int *) args; + if (prec < 0) + prec = 0; + args += sizeof(int); + c = *fmt++; + } + else + /* + * scan and convert precision field + */ + if (isdigit(c)) + while (isdigit(c)) { + prec *= 10; + prec += c - '0'; + c = *fmt++; + } + } + /* + * short and long modifiers. ignored for the moment + */ + if (c == 'h') { + c = *fmt++; + } + else + if (c == 'l') { + c = *fmt++; + } + /* + * check if it's a valid type "dioux..." + */ + if (strchr(typestr, c) != NULL) { + neg = 0; + switch (c) { + case 'd': + case 'i': { + int val = *(int *) args; + args += sizeof(int); + neg = (val < 0); + val = abs(val); + actwidth = strlen(utoa(val, buf, 10)); } + /* + * if negative or '+'/' ' flags set + */ + if (neg || (flags & FLAG_SIGNED) || (flags & FLAG_SPACE)) + actwidth++; + break; + case 'u': { + unsigned int uval = *(unsigned int *) args; + args += sizeof(unsigned int); + actwidth = strlen(utoa(uval, buf, 10)); } + /* + * if '+'/' ' flags set + */ + if ((flags & FLAG_SIGNED) || (flags & FLAG_SPACE)) + actwidth++; + break; + case 'x': + case 'X': { + int val = *(int *) args; + args += sizeof(int); + actwidth = strlen(utoa(val, buf, 16)); } + if (flags & FLAG_ALT) + actwidth += 2; + break; + case 'o': { + int val = *(int *) args; + args += sizeof(int); + actwidth = strlen(utoa(val, buf, 8)); } + if (flags & FLAG_ALT) + actwidth++; + break; + case 's': + s = *(char **) args; + args += sizeof(char *); + actwidth = strlen(s); + break; + case 'c': + buf[0] = *(char *) args; + buf[1] = '\0'; + args += sizeof(char); + actwidth = 1; + break; + /* + * dunno how to handle pointers - what's the format of + * linux86 pointers?! right now just prints "(ptr)" + */ + case 'p': + strcpy(buf, "(ptr)"); + args += sizeof(void *); + actwidth = strlen(buf); + s = buf; /* pretend we're a string */ + c = 's'; + break; +#ifndef STANDARD + case 'b': { + int val = *(int *) args; + args += sizeof(int); + actwidth = strlen(utoa(val, buf, 2)); } + break; +#endif + } + /* + * strings behave differently to the width.precision + * parameters, so handle separately. besides, we avoid + * an extra 'memcpy' to 'buf' + */ + if (c == 's') { + if (prec == 0) + prec = actwidth; + width = max(width, prec); + /* + * pad to the left if not left justified + */ + if (!(flags & FLAG_LEFTJUST)) { + for (i = width; i > prec; i--) + OUTC(' '); + } + /* + * print out entire string if no precision specified, otherwise + * that's our upper limit + */ + if (prec == 0) + for (; *s; s++) + OUTC(*s) /* no semicolon here */ + else + for (i = 0; i < prec; i++) + OUTC(s[i]); + } + else { + /* + * precision is as wide as width if it's not specified and + * the leading zero '0' flag is set, and left-justify is + * -not- set. c standard says left justify overrides the + * leading 0. + */ + if (prec == 0 && (flags & FLAG_PADZERO) && !(flags & FLAG_LEFTJUST)) + prec = width; + /* + * expand width.precision to fit the actual width. printf + * width specifies the -minimum-, and aside from the + * precision of %s fields, there's no way to specify maximum + */ + prec = max(prec, actwidth); + width = max(width, prec); + /* + * pad to the left if we're not left justified + */ + if (!(flags & FLAG_LEFTJUST)) { + for (i = width; i > prec; i--) + OUTC(' '); + } + /* + * check if we might need to print the sign + */ + if (strchr("diu", c) != NULL) { + if (neg) /* print if negative */ + OUTC('-') /* yes, no ';' here =) */ + else + if (flags & FLAG_SIGNED) /* or '+' specified */ + OUTC('+') /* nor here */ + else + if (flags & FLAG_SPACE) /* or ' ' specified */ + OUTC(' ') /* nor here */ + } + /* + * the alternate '#' flag is set. doesn't affect all though + */ + if (flags & FLAG_ALT) { + switch (c) { + case 'o': + OUTC('0'); /* leading zero for octals */ + break; + case 'x': + case 'X': /* prints 0x or 0X */ + OUTC('0'); + OUTC(c); + break; + } + } + /* + * fill the precision field with either spaces or zeroes, + * depending if we're printing numbers + */ + if (strchr("diuxXo", c) != NULL) + for (i = prec; i > actwidth; i--) + OUTC('0') + else + for (i = prec; i > actwidth; i--) + OUTC(' '); + /* + * print the field, except for 'X', which we convert to caps + */ + if (c != 'X') + for (f = buf; *f; f++) + OUTC(*f) /* none here either */ + else + for (f = buf; *f; f++) + OUTC(toupper(*f)); + } + /* + * if we're left justified, we now need to pad spaces to the + * right so that width will be correct + */ + if (flags & FLAG_LEFTJUST) + for (i = width; i > prec; i--) + OUTC(' '); + } + else { + /* + * miscellaneous %thingies + */ + switch (c) { + case '%': /* %% -> % */ + OUTC('%'); + break; + case 'n': /* %n writes current output count */ + *(*(int **) args) = n; + args += sizeof(int *); + break; + default: /* oops, got a bad %thingy */ + bad = 1; + } + } + c = *fmt++; + } + } +#ifndef STANDARD + /* + * dunno what the standard wants if the format string is badly + * formed, so i print (err) if the debug flag is set + */ + if (bad) { + OUTC('('); + OUTC('e'); + OUTC('r'); + OUTC('r'); + OUTC(')'); + } +#endif + return(n); +} + +#ifdef TEST + +#include <time.h> + +int main() +{ + static unsigned char xbuf[128], *x; + char *fmt = "%s, %s %d, %.*d:%.*d\n"; + int rv1, rv2, i, dt1, dt2; + clock_t t1, t2; + + x = xbuf; + *(char **) x = "Sun"; + x += sizeof(char *); + *(char **) x = "Feb"; + x += sizeof(char *); + *(int *) x = 18; + x += sizeof(int); + *(int *) x = 2; + x += sizeof(int); + *(int *) x = 10; + x += sizeof(int); + *(int *) x = 2; + x += sizeof(int); + *(int *) x = 56; + x += sizeof(int); + t1 = clock(); + for (i = 0; i < 1000; i++) + rv1 = _printf(stdout, fmt, xbuf); + t2 = clock(); + dt1 = t2 - t1; + + t1 = clock(); + for (i = 0; i < 1000; i++) + rv2 = printf(fmt, "Sun", "Feb", 18, 2, 10, 2, 56); + t2 = clock(); + dt2 = t2 - t1; + + printf("\nrv1: %d, rv2: %d, dt1: %d, dt2: %d\n", rv1, rv2, dt1, dt2); + return(0); +} + +#endif |