/*** *output.c - printf style output to a struct w4io * * Copyright (c) 1989-1991, Microsoft Corporation. All rights reserved. * *Purpose: * This file contains the code that does all the work for the * printf family of functions. It should not be called directly, only * by the *printf functions. We don't make any assumtions about the * sizes of ints, longs, shorts, or long doubles, but if types do overlap, we * also try to be efficient. We do assume that pointers are the same size * as either ints or longs. * *Revision History: * 06-01-89 PHG Module created * 08-28-89 JCR Added cast to get rid of warning (no object changes) * 02-15-90 GJF Fixed copyright * 10-03-90 WHB Defined LOCAL(x) to "static x" for local procedures * *******************************************************************************/ #include #include #include #include #include "wchar.h" #include "w4io.h" /* this macro defines a function which is private and as fast as possible: */ /* for example, in C 6.0, it might be static _fastcall . */ #define LOCAL(x) static x // 100390--WHB #define NOFLOATS // Win 4 doesn't need floating point /* int/long/short/pointer sizes */ /* the following should be set depending on the sizes of various types */ // FLAT or LARGE model is assumed #ifdef FLAT # define LONG_IS_INT 1 /* 1 means long is same size as int */ # define SHORT_IS_INT 0 /* 1 means short is same size as int */ # define PTR_IS_INT 1 /* 1 means ptr is same size as int */ # define PTR_IS_LONG 0 /* 1 means ptr is same size as long */ #else // LARGE model # define LONG_IS_INT 0 /* 1 means long is same size as int */ # define SHORT_IS_INT 1 /* 1 means short is same size as int */ # define PTR_IS_INT 0 /* 1 means ptr is same size as int */ # define PTR_IS_LONG 1 /* 1 means ptr is same size as long */ #endif #define LONGDOUBLE_IS_DOUBLE 0 /* 1 means long double is same as double */ #if LONG_IS_INT #define get_long_arg(x) (long)get_int_arg(x) #endif #if PTR_IS_INT #define get_ptr_arg(x) (void *)get_int_arg(x) #elif PTR_IS_LONG #define get_ptr_arg(x) (void *)get_long_arg(x) #else #error Size of pointer must be same as size of int or long #endif #ifndef NOFLOATS /* These are "fake" double and long doubles to fool the compiler, so we don't drag in floating point. */ typedef struct { char x[sizeof(double)]; } DOUBLE; typedef struct { char x[sizeof(long double)]; } LONGDOUBLE; #endif /* CONSTANTS */ //#define BUFFERSIZE CVTBUFSIZE /* buffer size for maximum double conv */ #define BUFFERSIZE 20 /* flag definitions */ #define FL_SIGN 0x0001 /* put plus or minus in front */ #define FL_SIGNSP 0x0002 /* put space or minus in front */ #define FL_LEFT 0x0004 /* left justify */ #define FL_LEADZERO 0x0008 /* pad with leading zeros */ #define FL_LONG 0x0010 /* long value given */ #define FL_SHORT 0x0020 /* short value given */ #define FL_SIGNED 0x0040 /* signed data given */ #define FL_ALTERNATE 0x0080 /* alternate form requested */ #define FL_NEGATIVE 0x0100 /* value is negative */ #define FL_FORCEOCTAL 0x0200 /* force leading '0' for octals */ #define FL_LONGDOUBLE 0x0400 /* long double value given */ #define FL_WIDE 0x0800 /* wide character/string given */ /* state definitions */ enum STATE { ST_NORMAL, /* normal state; outputting literal chars */ ST_PERCENT, /* just read '%' */ ST_FLAG, /* just read flag character */ ST_WIDTH, /* just read width specifier */ ST_DOT, /* just read '.' */ ST_PRECIS, /* just read precision specifier */ ST_SIZE, /* just read size specifier */ ST_TYPE /* just read type specifier */ }; #define NUMSTATES (ST_TYPE + 1) /* character type values */ enum CHARTYPE { CH_OTHER, /* character with no special meaning */ CH_PERCENT, /* '%' */ CH_DOT, /* '.' */ CH_STAR, /* '*' */ CH_ZERO, /* '0' */ CH_DIGIT, /* '1'..'9' */ CH_FLAG, /* ' ', '+', '-', '#' */ CH_SIZE, /* 'h', 'l', 'L', 'N', 'F' */ CH_TYPE /* type specifying character */ }; /* static data (read only, since we are re-entrant) */ char *nullstring = "(null)"; /* string to print on null ptr */ /* The state table. This table is actually two tables combined into one. */ /* The lower nybble of each byte gives the character class of any */ /* character; while the uper nybble of the byte gives the next state */ /* to enter. See the macros below the table for details. */ /* */ /* The table is generated by maketab.c -- use the maketab program to make */ /* changes. */ static char lookuptable[] = { 0x06, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x10, 0x00, 0x03, 0x06, 0x00, 0x06, 0x02, 0x10, 0x04, 0x45, 0x45, 0x45, 0x05, 0x05, 0x05, 0x05, 0x05, 0x35, 0x30, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x20, 0x28, 0x38, 0x50, 0x58, 0x07, 0x08, 0x00, 0x30, 0x30, 0x30, 0x57, 0x50, 0x07, 0x00, 0x00, 0x20, 0x20, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x70, 0x70, 0x78, 0x78, 0x78, 0x78, 0x08, 0x07, 0x08, 0x00, 0x00, 0x07, 0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x07, 0x08 }; #define find_char_class(c) \ ((c) < ' ' || (c) > 'x' ? \ CH_OTHER \ : \ lookuptable[(c)-' '] & 0xF) #define find_next_state(class, state) \ (lookuptable[(class) * NUMSTATES + (state)] >> 4) #if !LONG_IS_INT LOCAL(long) get_long_arg(va_list *pargptr); #endif LOCAL(int) get_int_arg(va_list *pargptr); LOCAL(void) writestring(char *string, int len, struct w4io *f, int *pcchwritten, int fwide); #ifndef NOFLOATS /* extern float convert routines */ typedef int (* PFI)(); extern PFI _cfltcvt_tab[5]; #define _cfltcvt(a,b,c,d,e) (*_cfltcvt_tab[0])(a,b,c,d,e) #define _cropzeros(a) (*_cfltcvt_tab[1])(a) #define _fassign(a,b,c) (*_cfltcvt_tab[2])(a,b,c) #define _forcdecpt(a) (*_cfltcvt_tab[3])(a) #define _positive(a) (*_cfltcvt_tab[4])(a) #define _cldcvt(a,b,c,d,e) (*_cfltcvt_tab[5])(a,b,c,d,e) #endif /*** *int w4iooutput(f, format, argptr) * *Purpose: * Output performs printf style output onto a stream. It is called by * printf/fprintf/sprintf/vprintf/vfprintf/vsprintf to so the dirty * work. In multi-thread situations, w4iooutput assumes that the given * stream is already locked. * * Algorithm: * The format string is parsed by using a finite state automaton * based on the current state and the current character read from * the format string. Thus, looping is on a per-character basis, * not a per conversion specifier basis. Once the format specififying * character is read, output is performed. * *Entry: * struct w4io *f - stream for output * char *format - printf style format string * va_list argptr - pointer to list of subsidiary arguments * *Exit: * Returns the number of characters written, or -1 if an output error * occurs. * *Exceptions: * *******************************************************************************/ int _cdecl w4iooutput(struct w4io *f, const char *format, va_list argptr) { int hexadd; /* offset to add to number to get 'a'..'f' */ char ch; /* character just read */ wchar_t wc; /* wide character temp */ wchar_t *pwc; /* wide character temp pointer */ int flags; /* flag word -- see #defines above for flag values */ enum STATE state; /* current state */ enum CHARTYPE chclass; /* class of current character */ int radix; /* current conversion radix */ int charsout; /* characters currently written so far, -1 = IO error */ int fldwidth; /* selected field with -- 0 means default */ int fwide; int precision; /* selected precision -- -1 means default */ char prefix[2]; /* numeric prefix -- up to two characters */ int prefixlen; /* length of prefix -- 0 means no prefix */ int capexp; /* non-zero = 'E' exponent signifiet, zero = 'e' */ int no_output; /* non-zero = prodcue no output for this specifier */ char *text; /* pointer text to be printed, not zero terminated */ int textlen; /* length of the text to be printed */ char buffer[BUFFERSIZE]; /* buffer for conversions */ charsout = 0; /* no characters written yet */ state = ST_NORMAL; /* starting state */ /* main loop -- loop while format character exist and no I/O errors */ while ((ch = *format++) != '\0' && charsout >= 0) { chclass = find_char_class(ch); /* find character class */ state = find_next_state(chclass, state); /* find next state */ /* execute code for each state */ switch (state) { case ST_NORMAL: /* normal state -- just write character */ f->writechar(ch, 1, f, &charsout); break; case ST_PERCENT: /* set default value of conversion parameters */ prefixlen = fldwidth = no_output = capexp = 0; flags = 0; precision = -1; fwide = 0; break; case ST_FLAG: /* set flag based on which flag character */ switch (ch) { case '-': flags |= FL_LEFT; /* '-' => left justify */ break; case '+': flags |= FL_SIGN; /* '+' => force sign indicator */ break; case ' ': flags |= FL_SIGNSP; /* ' ' => force sign or space */ break; case '#': flags |= FL_ALTERNATE; /* '#' => alternate form */ break; case '0': flags |= FL_LEADZERO; /* '0' => pad with leading zeros */ break; } break; case ST_WIDTH: /* update width value */ if (ch == '*') { /* get width from arg list */ fldwidth = get_int_arg(&argptr); if (fldwidth < 0) { /* ANSI says neg fld width means '-' flag and pos width */ flags |= FL_LEFT; fldwidth = -fldwidth; } } else { /* add digit to current field width */ fldwidth = fldwidth * 10 + (ch - '0'); } break; case ST_DOT: /* zero the precision, since dot with no number means 0 not default, according to ANSI */ precision = 0; break; case ST_PRECIS: /* update precison value */ if (ch == '*') { /* get precision from arg list */ precision = get_int_arg(&argptr); if (precision < 0) precision = -1; /* neg precision means default */ } else { /* add digit to current precision */ precision = precision * 10 + (ch - '0'); } break; case ST_SIZE: /* just read a size specifier, set the flags based on it */ switch (ch) { #if !LONG_IS_INT case 'l': flags |= FL_LONG; /* 'l' => long int */ break; #endif #if !LONGDOUBLE_IS_DOUBLE case 'L': flags |= FL_LONGDOUBLE; /* 'L' => long double */ break; #endif #if !SHORT_IS_INT case 'h': flags |= FL_SHORT; /* 'h' => short int */ break; #endif case 'w': flags |= FL_WIDE; /* 'w' => wide character */ break; } break; case ST_TYPE: /* we have finally read the actual type character, so we */ /* now format and "print" the output. We use a big switch */ /* statement that sets 'text' to point to the text that should */ /* be printed, and 'textlen' to the length of this text. */ /* Common code later on takes care of justifying it and */ /* other miscellaneous chores. Note that cases share code, */ /* in particular, all integer formatting is doen in one place. */ /* Look at those funky goto statements! */ switch (ch) { case 'c': { /* print a single character specified by int argument */ wc = (wchar_t) get_int_arg(&argptr); /* get char to print */ * (wchar_t *) buffer = wc; text = buffer; textlen = 1; /* print just a single character */ } break; case 'S': { /* print a Counted String */ struct string { short Length; short MaximumLength; char *Buffer; } *pstr; pstr = get_ptr_arg(&argptr); if (pstr == NULL || pstr->Buffer == NULL) { /* null ptr passed, use special string */ text = nullstring; textlen = strlen(text); flags &= ~FL_WIDE; } else { text = pstr->Buffer; /* The length field is a count of bytes, not characters. */ if (flags & FL_WIDE) textlen = pstr->Length / sizeof( wchar_t ); else textlen = pstr->Length; if (precision != -1) textlen = min( textlen, precision ); } } break; case 's': { /* print a string -- */ /* ANSI rules on how much of string to print: */ /* all if precision is default, */ /* min(precision, length) if precision given. */ /* prints '(null)' if a null string is passed */ int i; char *p; /* temps */ text = get_ptr_arg(&argptr); if (text == NULL) { /* null ptr passed, use special string */ text = nullstring; flags &= ~FL_WIDE; } /* At this point it is tempting to use strlen(), but */ /* if a precision is specified, we're not allowed to */ /* scan past there, because there might be no null */ /* at all. Thus, we must do our own scan. */ i = (precision == -1) ? INT_MAX : precision; /* scan for null upto i characters */ if (flags & FL_WIDE) { pwc = (wchar_t *) text; while (i-- && (wc = *pwc) && (wc & 0x00ff)) { ++pwc; if (wc & 0xff00) { // if high byte set, break; // error will be indicated } } textlen = pwc - (wchar_t *) text; /* length of string */ } else { p = text; while (i-- && *p) { ++p; } textlen = p - text; /* length of the string */ } } break; case 'n': { /* write count of characters seen so far into */ /* short/int/long thru ptr read from args */ void *p; /* temp */ p = get_ptr_arg(&argptr); /* store chars out into short/long/int depending on flags */ #if !LONG_IS_INT if (flags & FL_LONG) *(long *)p = charsout; else #endif #if !SHORT_IS_INT if (flags & FL_SHORT) *(short *)p = (short) charsout; else #endif *(int *)p = charsout; no_output = 1; /* force no output */ } break; #ifndef NOFLOATS case 'E': case 'G': capexp = 1; /* capitalize exponent */ ch += 'a' - 'A'; /* convert format char to lower */ /* DROP THROUGH */ case 'e': case 'f': case 'g': { /* floating point conversion -- we call cfltcvt routines */ /* to do the work for us. */ flags |= FL_SIGNED; /* floating point is signed conversion */ text = buffer; /* put result in buffer */ flags &= ~FL_WIDE; /* 8 bit string */ /* compute the precision value */ if (precision < 0) precision = 6; /* default precision: 6 */ else if (precision == 0 && ch == 'g') precision = 1; /* ANSI specified */ #if !LONGDOUBLE_IS_DOUBLE /* do the conversion */ if (flags & FL_LONGDOUBLE) { _cldcvt(argptr, text, ch, precision, capexp); va_arg(argptr, LONGDOUBLE); } else #endif { _cfltcvt(argptr, text, ch, precision, capexp); va_arg(argptr, DOUBLE); } /* '#' and precision == 0 means force a decimal point */ if ((flags & FL_ALTERNATE) && precision == 0) _forcdecpt(text); /* 'g' format means crop zero unless '#' given */ if (ch == 'g' && !(flags & FL_ALTERNATE)) _cropzeros(text); /* check if result was negative, save '-' for later */ /* and point to positive part (this is for '0' padding) */ if (*text == '-') { flags |= FL_NEGATIVE; ++text; } textlen = strlen(text); /* compute length of text */ } break; #endif // NOFLOATS case 'd': case 'i': /* signed decimal output */ flags |= FL_SIGNED; radix = 10; goto COMMON_INT; case 'u': radix = 10; goto COMMON_INT; case 'p': /* write a pointer -- this is like an integer or long */ /* except we force precision to pad with zeros and */ /* output in big hex. */ precision = 2 * sizeof(void *); /* number of hex digits needed */ #if !PTR_IS_INT flags |= FL_LONG; /* assume we're converting a long */ #endif /* DROP THROUGH to hex formatting */ case 'C': case 'X': /* unsigned upper hex output */ hexadd = 'A' - '9' - 1; /* set hexadd for uppercase hex */ goto COMMON_HEX; case 'x': /* unsigned lower hex output */ hexadd = 'a' - '9' - 1; /* set hexadd for lowercase hex */ /* DROP THROUGH TO COMMON_HEX */ COMMON_HEX: radix = 16; if (flags & FL_ALTERNATE) { /* alternate form means '0x' prefix */ prefix[0] = '0'; prefix[1] = (char)('x' - 'a' + '9' + 1 + hexadd); /* 'x' or 'X' */ prefixlen = 2; } goto COMMON_INT; case 'o': /* unsigned octal output */ radix = 8; if (flags & FL_ALTERNATE) { /* alternate form means force a leading 0 */ flags |= FL_FORCEOCTAL; } /* DROP THROUGH to COMMON_INT */ COMMON_INT: { /* This is the general integer formatting routine. */ /* Basically, we get an argument, make it positive */ /* if necessary, and convert it according to the */ /* correct radix, setting text and textlen */ /* appropriately. */ unsigned long number; /* number to convert */ int digit; /* ascii value of digit */ long l; /* temp long value */ /* 1. read argument into l, sign extend as needed */ #if !LONG_IS_INT if (flags & FL_LONG) l = get_long_arg(&argptr); else #endif #if !SHORT_IS_INT if (flags & FL_SHORT) { if (flags & FL_SIGNED) l = (short) get_int_arg(&argptr); /* sign extend */ else l = (unsigned short) get_int_arg(&argptr); /* zero-extend*/ } else #endif { if (flags & FL_SIGNED) l = get_int_arg(&argptr); /* sign extend */ else l = (unsigned int) get_int_arg(&argptr); /* zero-extend*/ } /* 2. check for negative; copy into number */ if ( (flags & FL_SIGNED) && l < 0) { number = -l; flags |= FL_NEGATIVE; /* remember negative sign */ } else { number = l; } /* 3. check precision value for default; non-default */ /* turns off 0 flag, according to ANSI. */ if (precision < 0) precision = 1; /* default precision */ else flags &= ~FL_LEADZERO; /* 4. Check if data is 0; if so, turn off hex prefix */ if (number == 0) prefixlen = 0; /* 5. Convert data to ASCII -- note if precision is zero */ /* and number is zero, we get no digits at all. */ text = &buffer[BUFFERSIZE-1]; // last digit at end of buffer flags &= ~FL_WIDE; // 8 bit characters while (precision-- > 0 || number != 0) { digit = (int)(number % radix) + '0'; number /= radix; /* reduce number */ if (digit > '9') { /* a hex digit, make it a letter */ digit += hexadd; } *text-- = (char)digit; /* store the digit */ } textlen = (char *)&buffer[BUFFERSIZE-1] - text; /* compute length of number */ ++text; /* text points to first digit now */ /* 6. Force a leading zero if FORCEOCTAL flag set */ if ((flags & FL_FORCEOCTAL) && (text[0] != '0' || textlen == 0)) { *--text = '0'; ++textlen; /* add a zero */ } } break; } /* At this point, we have done the specific conversion, and */ /* 'text' points to text to print; 'textlen' is length. Now we */ /* justify it, put on prefixes, leading zeros, and then */ /* print it. */ if (!no_output) { int padding; /* amount of padding, negative means zero */ if (flags & FL_SIGNED) { if (flags & FL_NEGATIVE) { /* prefix is a '-' */ prefix[0] = '-'; prefixlen = 1; } else if (flags & FL_SIGN) { /* prefix is '+' */ prefix[0] = '+'; prefixlen = 1; } else if (flags & FL_SIGNSP) { /* prefix is ' ' */ prefix[0] = ' '; prefixlen = 1; } } /* calculate amount of padding -- might be negative, */ /* but this will just mean zero */ padding = fldwidth - textlen - prefixlen; /* put out the padding, prefix, and text, in the correct order */ if (!(flags & (FL_LEFT | FL_LEADZERO))) { /* pad on left with blanks */ f->writechar(' ', padding, f, &charsout); } /* write prefix */ writestring(prefix, prefixlen, f, &charsout, 0); if ((flags & FL_LEADZERO) && !(flags & FL_LEFT)) { /* write leading zeros */ f->writechar('0', padding, f, &charsout); } /* write text */ writestring(text, textlen, f, &charsout, flags & FL_WIDE); if (flags & FL_LEFT) { /* pad on right with blanks */ f->writechar(' ', padding, f, &charsout); } /* we're done! */ } break; } } return charsout; /* return value = number of characters written */ } /*** *int get_int_arg(va_list pargptr) * *Purpose: * Gets an int argument off the given argument list and updates *pargptr. * *Entry: * va_list pargptr - pointer to argument list; updated by function * *Exit: * Returns the integer argument read from the argument list. * *Exceptions: * *******************************************************************************/ LOCAL(int) get_int_arg(va_list *pargptr) { return va_arg(*pargptr, int); } /*** *long get_long_arg(va_list pargptr) * *Purpose: * Gets an long argument off the given argument list and updates pargptr. * *Entry: * va_list pargptr - pointer to argument list; updated by function * *Exit: * Returns the long argument read from the argument list. * *Exceptions: * *******************************************************************************/ #if !LONG_IS_INT LOCAL(long) get_long_arg(va_list *pargptr) { return va_arg(*pargptr, long); } #endif /*** *void writestring(char *string, int len, struct w4io *f, int *pcchwritten, int fwide) * *Purpose: * Writes a string of the given length to the given file. If no error occurs, * then *pcchwritten is incremented by len; otherwise, *pcchwritten is set * to -1. If len is negative, it is treated as zero. * *Entry: * char *string - string to write (NOT null-terminated) * int len - length of string * struct w4io *f - file to write to * int *pcchwritten - pointer to integer to update with total chars written * int fwide - wide character flag * *Exit: * No return value. * *Exceptions: * *******************************************************************************/ LOCAL(void) writestring( char *string, int len, struct w4io *f, int *pcchwritten, int fwide) { wchar_t *pwc; //printf("string: str=%.*s, len=%d, cch=%d, f=%d\n", len, string, len, *pcchwritten, fwide); if (fwide) { pwc = (wchar_t *) string; while (len-- > 0) { if (*pwc & 0xff00) { f->writechar('^', 1, f, pcchwritten); } f->writechar((char) *pwc++, 1, f, pcchwritten); } } else { while (len-- > 0) { f->writechar(*string++, 1, f, pcchwritten); } } }