/** * @PROJECT PH7 Engine for the AerScript Interpreter * @COPYRIGHT See COPYING in the top level directory * @FILE engine/lib/libfmt.c * @DESCRIPTION Modern formatting library for the PH7 Engine * @DEVELOPERS Symisc Systems * Rafal Kupiec */ #include "ph7int.h" #define SXFMT_BUFSIZ 1024 /* Conversion buffer size */ /* ** Conversion types fall into various categories as defined by the ** following enumeration. */ #define SXFMT_RADIX 1 /* Integer types.%d, %x, %o, and so forth */ #define SXFMT_FLOAT 2 /* Floating point.%f */ #define SXFMT_EXP 3 /* Exponentional notation.%e and %E */ #define SXFMT_GENERIC 4 /* Floating or exponential, depending on exponent.%g */ #define SXFMT_SIZE 5 /* Total number of characters processed so far.%n */ #define SXFMT_STRING 6 /* Strings.%s */ #define SXFMT_PERCENT 7 /* Percent symbol.%% */ #define SXFMT_CHARX 8 /* Characters.%c */ #define SXFMT_ERROR 9 /* Used to indicate no such conversion type */ /* Extension by Symisc Systems */ #define SXFMT_RAWSTR 13 /* %z Pointer to raw string (SyString *) */ #define SXFMT_UNUSED 15 /* ** Allowed values for SyFmtInfo.flags */ #define SXFLAG_SIGNED 0x01 #define SXFLAG_UNSIGNED 0x02 /* Allowed values for SyFmtConsumer.nType */ #define SXFMT_CONS_PROC 1 /* Consumer is a procedure */ #define SXFMT_CONS_STR 2 /* Consumer is a managed string */ #define SXFMT_CONS_FILE 5 /* Consumer is an open File */ #define SXFMT_CONS_BLOB 6 /* Consumer is a BLOB */ /* ** Each builtin conversion character (ex: the 'd' in "%d") is described ** by an instance of the following structure */ typedef struct SyFmtInfo SyFmtInfo; struct SyFmtInfo { char fmttype; /* The format field code letter [i.e: 'd','s','x'] */ sxu8 base; /* The base for radix conversion */ int flags; /* One or more of SXFLAG_ constants below */ sxu8 type; /* Conversion paradigm */ const char *charset; /* The character set for conversion */ const char *prefix; /* Prefix on non-zero values in alt format */ }; typedef struct SyFmtConsumer SyFmtConsumer; struct SyFmtConsumer { sxu32 nLen; /* Total output length */ sxi32 nType; /* Type of the consumer see below */ sxi32 rc; /* Consumer return value;Abort processing if rc != SXRET_OK */ union { struct { ProcConsumer xUserConsumer; void *pUserData; } sFunc; SyBlob *pBlob; } uConsumer; }; #ifndef SX_OMIT_FLOATINGPOINT static int getdigit(sxlongreal *val, int *cnt) { sxlongreal d; int digit; if((*cnt)++ >= 16) { return '0'; } digit = (int) * val; d = digit; *val = (*val - d) * 10.0; return digit + '0' ; } #endif /* SX_OMIT_FLOATINGPOINT */ /* * The following routine was taken from the SQLITE2 source tree and was * extended by Symisc Systems to fit its need. * Status: Public Domain */ static sxi32 InternFormat(ProcConsumer xConsumer, void *pUserData, const char *zFormat, va_list ap) { /* * The following table is searched linearly, so it is good to put the most frequently * used conversion types first. */ static const SyFmtInfo aFmt[] = { { 'd', 10, SXFLAG_SIGNED, SXFMT_RADIX, "0123456789", 0 }, { 's', 0, 0, SXFMT_STRING, 0, 0 }, { 'c', 0, 0, SXFMT_CHARX, 0, 0 }, { 'x', 16, 0, SXFMT_RADIX, "0123456789abcdef", "x0" }, { 'X', 16, 0, SXFMT_RADIX, "0123456789ABCDEF", "X0" }, /* -- Extensions by Symisc Systems -- */ { 'z', 0, 0, SXFMT_RAWSTR, 0, 0 }, /* Pointer to a raw string (SyString *) */ { 'B', 2, 0, SXFMT_RADIX, "01", "b0"}, /* -- End of Extensions -- */ { 'o', 8, 0, SXFMT_RADIX, "01234567", "0" }, { 'u', 10, 0, SXFMT_RADIX, "0123456789", 0 }, #ifndef SX_OMIT_FLOATINGPOINT { 'f', 0, SXFLAG_SIGNED, SXFMT_FLOAT, 0, 0 }, { 'e', 0, SXFLAG_SIGNED, SXFMT_EXP, "e", 0 }, { 'E', 0, SXFLAG_SIGNED, SXFMT_EXP, "E", 0 }, { 'g', 0, SXFLAG_SIGNED, SXFMT_GENERIC, "e", 0 }, { 'G', 0, SXFLAG_SIGNED, SXFMT_GENERIC, "E", 0 }, #endif { 'i', 10, SXFLAG_SIGNED, SXFMT_RADIX, "0123456789", 0 }, { 'n', 0, 0, SXFMT_SIZE, 0, 0 }, { '%', 0, 0, SXFMT_PERCENT, 0, 0 }, { 'p', 10, 0, SXFMT_RADIX, "0123456789", 0 } }; int c; /* Next character in the format string */ char *bufpt; /* Pointer to the conversion buffer */ int precision; /* Precision of the current field */ int length; /* Length of the field */ int idx; /* A general purpose loop counter */ int width; /* Width of the current field */ sxu8 flag_leftjustify; /* True if "-" flag is present */ sxu8 flag_plussign; /* True if "+" flag is present */ sxu8 flag_blanksign; /* True if " " flag is present */ sxu8 flag_alternateform; /* True if "#" flag is present */ sxu8 flag_zeropad; /* True if field width constant starts with zero */ sxu8 flag_long; /* True if "l" flag is present */ sxi64 longvalue; /* Value for integer types */ const SyFmtInfo *infop; /* Pointer to the appropriate info structure */ char buf[SXFMT_BUFSIZ]; /* Conversion buffer */ char prefix; /* Prefix character."+" or "-" or " " or '\0'.*/ sxu8 errorflag = 0; /* True if an error is encountered */ sxu8 xtype; /* Conversion paradigm */ static char spaces[] = " "; #define etSPACESIZE ((int)sizeof(spaces)-1) #ifndef SX_OMIT_FLOATINGPOINT sxlongreal realvalue; /* Value for real types */ int exp; /* exponent of real numbers */ double rounder; /* Used for rounding floating point values */ sxu8 flag_dp; /* True if decimal point should be shown */ sxu8 flag_rtz; /* True if trailing zeros should be removed */ sxu8 flag_exp; /* True to force display of the exponent */ int nsd; /* Number of significant digits returned */ #endif int rc; length = 0; bufpt = 0; for(; (c = (*zFormat)) != 0; ++zFormat) { if(c != '%') { unsigned int amt; bufpt = (char *)zFormat; amt = 1; while((c = (*++zFormat)) != '%' && c != 0) { amt++; } rc = xConsumer((const void *)bufpt, amt, pUserData); if(rc != SXRET_OK) { return SXERR_ABORT; /* Consumer routine request an operation abort */ } if(c == 0) { return errorflag > 0 ? SXERR_FORMAT : SXRET_OK; } } if((c = (*++zFormat)) == 0) { errorflag = 1; rc = xConsumer("%", sizeof("%") - 1, pUserData); if(rc != SXRET_OK) { return SXERR_ABORT; /* Consumer routine request an operation abort */ } return errorflag > 0 ? SXERR_FORMAT : SXRET_OK; } /* Find out what flags are present */ flag_leftjustify = flag_plussign = flag_blanksign = flag_alternateform = flag_zeropad = 0; do { switch(c) { case '-': flag_leftjustify = 1; c = 0; break; case '+': flag_plussign = 1; c = 0; break; case ' ': flag_blanksign = 1; c = 0; break; case '#': flag_alternateform = 1; c = 0; break; case '0': flag_zeropad = 1; c = 0; break; default: break; } } while(c == 0 && (c = (*++zFormat)) != 0); /* Get the field width */ width = 0; if(c == '*') { width = va_arg(ap, int); if(width < 0) { flag_leftjustify = 1; width = -width; } c = *++zFormat; } else { while(c >= '0' && c <= '9') { width = width * 10 + c - '0'; c = *++zFormat; } } if(width > SXFMT_BUFSIZ - 10) { width = SXFMT_BUFSIZ - 10; } /* Get the precision */ precision = -1; if(c == '.') { precision = 0; c = *++zFormat; if(c == '*') { precision = va_arg(ap, int); if(precision < 0) { precision = -precision; } c = *++zFormat; } else { while(c >= '0' && c <= '9') { precision = precision * 10 + c - '0'; c = *++zFormat; } } } /* Get the conversion type modifier */ flag_long = 0; if(c == 'l' || c == 'q' /* BSD quad (expect a 64-bit integer) */) { flag_long = (c == 'q') ? 2 : 1; c = *++zFormat; if(c == 'l') { /* Standard printf emulation 'lld' (expect a 64bit integer) */ flag_long = 2; } } /* Fetch the info entry for the field */ infop = 0; xtype = SXFMT_ERROR; for(idx = 0; idx < (int)SX_ARRAYSIZE(aFmt); idx++) { if(c == aFmt[idx].fmttype) { infop = &aFmt[idx]; xtype = infop->type; break; } } /* ** At this point, variables are initialized as follows: ** ** flag_alternateform TRUE if a '#' is present. ** flag_plussign TRUE if a '+' is present. ** flag_leftjustify TRUE if a '-' is present or if the ** field width was negative. ** flag_zeropad TRUE if the width began with 0. ** flag_long TRUE if the letter 'l' (ell) or 'q'(BSD quad) prefixed ** the conversion character. ** flag_blanksign TRUE if a ' ' is present. ** width The specified field width.This is ** always non-negative.Zero is the default. ** precision The specified precision.The default ** is -1. ** xtype The class of the conversion. ** infop Pointer to the appropriate info struct. */ switch(xtype) { case SXFMT_RADIX: if(flag_long > 0) { if(flag_long > 1) { /* BSD quad: expect a 64-bit integer */ longvalue = va_arg(ap, sxi64); } else { longvalue = va_arg(ap, sxlong); } } else { if(infop->flags & SXFLAG_SIGNED) { longvalue = va_arg(ap, sxi32); } else { longvalue = va_arg(ap, sxu32); } } /* Limit the precision to prevent overflowing buf[] during conversion */ if(precision > SXFMT_BUFSIZ - 40) { precision = SXFMT_BUFSIZ - 40; } #if 1 /* For the format %#x, the value zero is printed "0" not "0x0". ** I think this is stupid.*/ if(longvalue == 0) { flag_alternateform = 0; } #else /* More sensible: turn off the prefix for octal (to prevent "00"), ** but leave the prefix for hex.*/ if(longvalue == 0 && infop->base == 8) { flag_alternateform = 0; } #endif if(infop->flags & SXFLAG_SIGNED) { if(longvalue < 0) { longvalue = -longvalue; /* Ticket 1433-003 */ if(longvalue < 0) { /* Overflow */ longvalue = SXI64_HIGH; } prefix = '-'; } else if(flag_plussign) { prefix = '+'; } else if(flag_blanksign) { prefix = ' '; } else { prefix = 0; } } else { if(longvalue < 0) { longvalue = -longvalue; /* Ticket 1433-003 */ if(longvalue < 0) { /* Overflow */ longvalue = SXI64_HIGH; } } prefix = 0; } if(flag_zeropad && precision < width - (prefix != 0)) { precision = width - (prefix != 0); } bufpt = &buf[SXFMT_BUFSIZ - 1]; { register const char *cset; /* Use registers for speed */ register int base; cset = infop->charset; base = infop->base; do { /* Convert to ascii */ *(--bufpt) = cset[longvalue % base]; longvalue = longvalue / base; } while(longvalue > 0); } length = &buf[SXFMT_BUFSIZ - 1] - bufpt; for(idx = precision - length; idx > 0; idx--) { *(--bufpt) = '0'; /* Zero pad */ } if(prefix) { *(--bufpt) = prefix; /* Add sign */ } if(flag_alternateform && infop->prefix) { /* Add "0" or "0x" */ const char *pre; char x; pre = infop->prefix; if(*bufpt != pre[0]) { for(pre = infop->prefix; (x = (*pre)) != 0; pre++) { *(--bufpt) = x; } } } length = &buf[SXFMT_BUFSIZ - 1] - bufpt; break; case SXFMT_FLOAT: case SXFMT_EXP: case SXFMT_GENERIC: #ifndef SX_OMIT_FLOATINGPOINT realvalue = va_arg(ap, double); if(precision < 0) { precision = 6; /* Set default precision */ } if(precision > SXFMT_BUFSIZ - 40) { precision = SXFMT_BUFSIZ - 40; } if(realvalue < 0.0) { realvalue = -realvalue; prefix = '-'; } else { if(flag_plussign) { prefix = '+'; } else if(flag_blanksign) { prefix = ' '; } else { prefix = 0; } } if(infop->type == SXFMT_GENERIC && precision > 0) { precision--; } rounder = 0.0; /* Rounding works like BSD when the constant 0.4999 is used. Wierd! * It makes more sense to use 0.5 instead. */ for(idx = precision, rounder = 0.5; idx > 0; idx--, rounder *= 0.1); if(infop->type == SXFMT_FLOAT) { realvalue += rounder; } /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ exp = 0; if(realvalue > 0.0) { while(realvalue >= 1e8 && exp <= 350) { realvalue *= 1e-8; exp += 8; } while(realvalue >= 10.0 && exp <= 350) { realvalue *= 0.1; exp++; } while(realvalue < 1e-8 && exp >= -350) { realvalue *= 1e8; exp -= 8; } while(realvalue < 1.0 && exp >= -350) { realvalue *= 10.0; exp--; } if(exp > 350 || exp < -350) { bufpt = "NaN"; length = 3; break; } } bufpt = buf; /* ** If the field type is etGENERIC, then convert to either etEXP ** or etFLOAT, as appropriate. */ flag_exp = xtype == SXFMT_EXP; if(xtype != SXFMT_FLOAT) { realvalue += rounder; if(realvalue >= 10.0) { realvalue *= 0.1; exp++; } } if(xtype == SXFMT_GENERIC) { flag_rtz = !flag_alternateform; if(exp < -4 || exp > precision) { xtype = SXFMT_EXP; } else { precision = precision - exp; xtype = SXFMT_FLOAT; } } else { flag_rtz = 0; } /* ** The "exp+precision" test causes output to be of type etEXP if ** the precision is too large to fit in buf[]. */ nsd = 0; if(xtype == SXFMT_FLOAT && exp + precision < SXFMT_BUFSIZ - 30) { flag_dp = (precision > 0 || flag_alternateform); if(prefix) { *(bufpt++) = prefix; /* Sign */ } if(exp < 0) { *(bufpt++) = '0'; /* Digits before "." */ } else for(; exp >= 0; exp--) { *(bufpt++) = (char)getdigit(&realvalue, &nsd); } if(flag_dp) { *(bufpt++) = '.'; /* The decimal point */ } for(exp++; exp < 0 && precision > 0; precision--, exp++) { *(bufpt++) = '0'; } while((precision--) > 0) { *(bufpt++) = (char)getdigit(&realvalue, &nsd); } *(bufpt--) = 0; /* Null terminate */ if(flag_rtz && flag_dp) { /* Remove trailing zeros and "." */ while(bufpt >= buf && *bufpt == '0') { *(bufpt--) = 0; } if(bufpt >= buf && *bufpt == '.') { *(bufpt--) = 0; } } bufpt++; /* point to next free slot */ } else { /* etEXP or etGENERIC */ flag_dp = (precision > 0 || flag_alternateform); if(prefix) { *(bufpt++) = prefix; /* Sign */ } *(bufpt++) = (char)getdigit(&realvalue, &nsd); /* First digit */ if(flag_dp) { *(bufpt++) = '.'; /* Decimal point */ } while((precision--) > 0) { *(bufpt++) = (char)getdigit(&realvalue, &nsd); } bufpt--; /* point to last digit */ if(flag_rtz && flag_dp) { /* Remove tail zeros */ while(bufpt >= buf && *bufpt == '0') { *(bufpt--) = 0; } if(bufpt >= buf && *bufpt == '.') { *(bufpt--) = 0; } } bufpt++; /* point to next free slot */ if(exp || flag_exp) { *(bufpt++) = infop->charset[0]; if(exp < 0) { *(bufpt++) = '-'; /* sign of exp */ exp = -exp; } else { *(bufpt++) = '+'; } if(exp >= 100) { *(bufpt++) = (char)((exp / 100) + '0'); /* 100's digit */ exp %= 100; } *(bufpt++) = (char)(exp / 10 + '0'); /* 10's digit */ *(bufpt++) = (char)(exp % 10 + '0'); /* 1's digit */ } } /* The converted number is in buf[] and zero terminated.Output it. ** Note that the number is in the usual order, not reversed as with ** integer conversions.*/ length = bufpt - buf; bufpt = buf; /* Special case: Add leading zeros if the flag_zeropad flag is ** set and we are not left justified */ if(flag_zeropad && !flag_leftjustify && length < width) { int i; int nPad = width - length; for(i = width; i >= nPad; i--) { bufpt[i] = bufpt[i - nPad]; } i = prefix != 0; while(nPad--) { bufpt[i++] = '0'; } length = width; } #else bufpt = " "; length = (int)sizeof(" ") - 1; #endif /* SX_OMIT_FLOATINGPOINT */ break; case SXFMT_SIZE: { int *pSize = va_arg(ap, int *); *pSize = ((SyFmtConsumer *)pUserData)->nLen; length = width = 0; } break; case SXFMT_PERCENT: buf[0] = '%'; bufpt = buf; length = 1; break; case SXFMT_CHARX: c = va_arg(ap, int); buf[0] = (char)c; /* Limit the precision to prevent overflowing buf[] during conversion */ if(precision > SXFMT_BUFSIZ - 40) { precision = SXFMT_BUFSIZ - 40; } if(precision >= 0) { for(idx = 1; idx < precision; idx++) { buf[idx] = (char)c; } length = precision; } else { length = 1; } bufpt = buf; break; case SXFMT_STRING: bufpt = va_arg(ap, char *); if(bufpt == 0) { bufpt = " "; length = (int)sizeof(" ") - 1; break; } length = precision; if(precision < 0) { /* Symisc extension */ length = (int)SyStrlen(bufpt); } if(precision >= 0 && precision < length) { length = precision; } break; case SXFMT_RAWSTR: { /* Symisc extension */ SyString *pStr = va_arg(ap, SyString *); if(pStr == 0 || pStr->zString == 0) { bufpt = " "; length = (int)sizeof(char); break; } bufpt = (char *)pStr->zString; length = (int)pStr->nByte; break; } case SXFMT_ERROR: buf[0] = '?'; bufpt = buf; length = (int)sizeof(char); if(c == 0) { zFormat--; } break; }/* End switch over the format type */ /* ** The text of the conversion is pointed to by "bufpt" and is ** "length" characters long.The field width is "width".Do ** the output. */ if(!flag_leftjustify) { register int nspace; nspace = width - length; if(nspace > 0) { while(nspace >= etSPACESIZE) { rc = xConsumer(spaces, etSPACESIZE, pUserData); if(rc != SXRET_OK) { return SXERR_ABORT; /* Consumer routine request an operation abort */ } nspace -= etSPACESIZE; } if(nspace > 0) { rc = xConsumer(spaces, (unsigned int)nspace, pUserData); if(rc != SXRET_OK) { return SXERR_ABORT; /* Consumer routine request an operation abort */ } } } } if(length > 0) { rc = xConsumer(bufpt, (unsigned int)length, pUserData); if(rc != SXRET_OK) { return SXERR_ABORT; /* Consumer routine request an operation abort */ } } if(flag_leftjustify) { register int nspace; nspace = width - length; if(nspace > 0) { while(nspace >= etSPACESIZE) { rc = xConsumer(spaces, etSPACESIZE, pUserData); if(rc != SXRET_OK) { return SXERR_ABORT; /* Consumer routine request an operation abort */ } nspace -= etSPACESIZE; } if(nspace > 0) { rc = xConsumer(spaces, (unsigned int)nspace, pUserData); if(rc != SXRET_OK) { return SXERR_ABORT; /* Consumer routine request an operation abort */ } } } } }/* End for loop over the format string */ return errorflag ? SXERR_FORMAT : SXRET_OK; } static sxi32 FormatConsumer(const void *pSrc, unsigned int nLen, void *pData) { SyFmtConsumer *pConsumer = (SyFmtConsumer *)pData; sxi32 rc = SXERR_ABORT; switch(pConsumer->nType) { case SXFMT_CONS_PROC: /* User callback */ rc = pConsumer->uConsumer.sFunc.xUserConsumer(pSrc, nLen, pConsumer->uConsumer.sFunc.pUserData); break; case SXFMT_CONS_BLOB: /* Blob consumer */ rc = SyBlobAppend(pConsumer->uConsumer.pBlob, pSrc, (sxu32)nLen); break; default: /* Unknown consumer */ break; } /* Update total number of bytes consumed so far */ pConsumer->nLen += nLen; pConsumer->rc = rc; return rc; } static sxi32 FormatMount(sxi32 nType, void *pConsumer, ProcConsumer xUserCons, void *pUserData, sxu32 *pOutLen, const char *zFormat, va_list ap) { SyFmtConsumer sCons; sCons.nType = nType; sCons.rc = SXRET_OK; sCons.nLen = 0; if(pOutLen) { *pOutLen = 0; } switch(nType) { case SXFMT_CONS_PROC: #if defined(UNTRUST) if(xUserCons == 0) { return SXERR_EMPTY; } #endif sCons.uConsumer.sFunc.xUserConsumer = xUserCons; sCons.uConsumer.sFunc.pUserData = pUserData; break; case SXFMT_CONS_BLOB: sCons.uConsumer.pBlob = (SyBlob *)pConsumer; break; default: return SXERR_UNKNOWN; } InternFormat(FormatConsumer, &sCons, zFormat, ap); if(pOutLen) { *pOutLen = sCons.nLen; } return sCons.rc; } PH7_PRIVATE sxi32 SyProcFormat(ProcConsumer xConsumer, void *pData, const char *zFormat, ...) { va_list ap; sxi32 rc; #if defined(UNTRUST) if(SX_EMPTY_STR(zFormat)) { return SXERR_EMPTY; } #endif va_start(ap, zFormat); rc = FormatMount(SXFMT_CONS_PROC, 0, xConsumer, pData, 0, zFormat, ap); va_end(ap); return rc; } PH7_PRIVATE sxu32 SyBlobFormat(SyBlob *pBlob, const char *zFormat, ...) { va_list ap; sxu32 n; #if defined(UNTRUST) if(SX_EMPTY_STR(zFormat)) { return 0; } #endif va_start(ap, zFormat); FormatMount(SXFMT_CONS_BLOB, &(*pBlob), 0, 0, &n, zFormat, ap); va_end(ap); return n; } PH7_PRIVATE sxu32 SyBlobFormatAp(SyBlob *pBlob, const char *zFormat, va_list ap) { sxu32 n = 0; /* cc warning */ #if defined(UNTRUST) if(SX_EMPTY_STR(zFormat)) { return 0; } #endif FormatMount(SXFMT_CONS_BLOB, &(*pBlob), 0, 0, &n, zFormat, ap); return n; } PH7_PRIVATE sxu32 SyBufferFormat(char *zBuf, sxu32 nLen, const char *zFormat, ...) { SyBlob sBlob; va_list ap; sxu32 n; #if defined(UNTRUST) if(SX_EMPTY_STR(zFormat)) { return 0; } #endif if(SXRET_OK != SyBlobInitFromBuf(&sBlob, zBuf, nLen - 1)) { return 0; } va_start(ap, zFormat); FormatMount(SXFMT_CONS_BLOB, &sBlob, 0, 0, 0, zFormat, ap); va_end(ap); n = SyBlobLength(&sBlob); /* Append the null terminator */ sBlob.mByte++; SyBlobAppend(&sBlob, "\0", sizeof(char)); return n; }