/**************************************************************************** * libs/libc/stdio/lib_libvsprintf.c * * Copyright (c) 2002, Alexander Popov (sasho@vip.bg) * Copyright (c) 2002,2004,2005 Joerg Wunsch * Copyright (c) 2005, Helmut Wallner * Copyright (c) 2007, Dmitry Xmelkov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include "lib_dtoa_engine.h" #include "lib_ultoa_invert.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* CONFIG_LIBC_LONG_LONG is not a valid selection of the compiler does not * support long long types. */ #ifndef CONFIG_HAVE_LONG_LONG # undef CONFIG_LIBC_LONG_LONG #endif /* [Re]define putc() */ #ifdef putc # undef putc #endif #define putc(c,stream) (total_len++, (stream)->put(stream, c)) /* Order is relevant here and matches order in format string */ #define FL_ZFILL 0x0001 #define FL_PLUS 0x0002 #define FL_SPACE 0x0004 #define FL_LPAD 0x0008 #define FL_ALT 0x0010 #define FL_ARGNUMBER 0x0020 #define FL_ASTERISK 0x0040 #define FL_WIDTH 0x0080 #define FL_PREC 0x0100 #define FL_LONG 0x0200 #define FL_SHORT 0x0400 #define FL_REPD_TYPE 0x0800 #define FL_NEGATIVE 0x1000 /* The next 2 groups are Exclusive Or */ #define FL_ALTUPP 0x2000 #define FL_ALTHEX 0x4000 #define FL_FLTUPP 0x2000 #define FL_FLTEXP 0x4000 #define FL_FLTFIX 0x8000 #define TYPE_INT 1 #define TYPE_LONG 2 #define TYPE_LONG_LONG 3 #define TYPE_DOUBLE 4 #define TYPE_CHAR_POINTER 5 /* Support special access to CODE-space strings for Harvard architectures */ #ifdef CONFIG_ARCH_ROMGETC # define fmt_char(fmt) up_romgetc((fmt)++) #else # define fmt_char(fmt) (*(fmt)++) #endif /**************************************************************************** * Private Types ****************************************************************************/ struct arg { unsigned char type; union { unsigned int u; unsigned long ul; #ifdef CONFIG_LIBC_LONG_LONG unsigned long long ull; #endif double d; FAR char *cp; } value; }; /**************************************************************************** * Private Constant Data ****************************************************************************/ static const char g_nullstring[] = "(null)"; /**************************************************************************** * Public Functions ****************************************************************************/ static int vsprintf_internal(FAR struct lib_outstream_s *stream, FAR struct arg *arglist, int numargs, FAR const IPTR char *fmt, va_list ap) { unsigned char c; /* Holds a char from the format string */ uint16_t flags; int width; int prec; union { #if defined (CONFIG_LIBC_LONG_LONG) || (ULONG_MAX > 4294967295UL) unsigned char __buf[22]; /* Size for -1 in octal, without '\0' */ #else unsigned char __buf[11]; /* Size for -1 in octal, without '\0' */ #endif #ifdef CONFIG_LIBC_FLOATINGPOINT struct dtoa_s __dtoa; #endif } u; #define buf (u.__buf) #define _dtoa (u.__dtoa) FAR const char *pnt; size_t size; unsigned char len; int total_len = 0; #ifdef CONFIG_LIBC_NUMBERED_ARGS int argnumber; #endif for (; ; ) { for (; ; ) { c = fmt_char(fmt); if (c == '\0') { goto ret; } if (c == '%') { c = fmt_char(fmt); if (c != '%') { break; } } #ifdef CONFIG_LIBC_NUMBERED_ARGS if (stream != NULL) { putc(c, stream); } #else putc(c, stream); #endif } flags = 0; width = 0; prec = 0; do { if (flags < FL_ASTERISK) { switch (c) { case '0': flags |= FL_ZFILL; continue; case '+': flags |= FL_PLUS; /* FALLTHROUGH */ case ' ': flags |= FL_SPACE; continue; case '-': flags |= FL_LPAD; continue; case '#': flags |= FL_ALT; continue; } } if (flags < FL_LONG) { #ifdef CONFIG_LIBC_NUMBERED_ARGS if (c == '$') { if ((flags & FL_ARGNUMBER) == 0) { /* No other flag except FL_WIDTH or FL_ZFILL (leading * zeros) and argument number must be at least 1 */ if ((flags & ~(FL_WIDTH | FL_ZFILL)) != 0 || width == 0) { goto ret; } /* It had been the argument number. */ argnumber = width; width = 0; flags = FL_ARGNUMBER; } else if ((flags & FL_ASTERISK) != 0) { int index; flags &= ~FL_ASTERISK; if ((flags & FL_PREC) == 0) { index = width; } else { index = prec; } if (index > 0 && index <= numargs) { if (stream == NULL) { arglist[index-1].type = TYPE_INT; if (index > total_len) { total_len = index; } } else { if ((flags & FL_PREC) == 0) { width = (int)arglist[index-1].value.u; } else { prec = (int)arglist[index-1].value.u; } } } else { goto ret; } } else { goto ret; } continue; } #endif if (c >= '0' && c <= '9') { c -= '0'; if ((flags & FL_PREC) != 0) { prec = 10 * prec + c; continue; } width = 10 * width + c; flags |= FL_WIDTH; continue; } if (c == '*') { #ifdef CONFIG_LIBC_NUMBERED_ARGS if ((flags & FL_ARGNUMBER) != 0) { flags |= FL_ASTERISK; continue; } #endif if ((flags & FL_PREC) != 0) { prec = va_arg(ap, int); if (prec < 0) { prec = 0; } } else { width = va_arg(ap, int); flags |= FL_WIDTH; if (width < 0) { width = -width; flags |= FL_LPAD; } } continue; } if (c == '.') { if ((flags & FL_PREC) != 0) { goto ret; } flags |= FL_PREC; continue; } } if (c == 'l') { if ((flags & FL_LONG) != 0) { flags |= FL_REPD_TYPE; } flags |= FL_LONG; flags &= ~FL_SHORT; continue; } if (c == 'h') { if ((flags & FL_SHORT) != 0) { flags |= FL_REPD_TYPE; } flags |= FL_SHORT; flags &= ~FL_LONG; continue; } break; } while ((c = fmt_char(fmt)) != 0); /* Only a format character is valid. */ #if 'F' != 'E'+1 || 'G' != 'F'+1 || 'f' != 'e'+1 || 'g' != 'f'+1 # error #endif if (c == 'p') { /* Determine size of pointer and set flags accordingly */ flags &= ~(FL_LONG | FL_REPD_TYPE); #ifdef CONFIG_LIBC_LONG_LONG if (sizeof(void *) == sizeof(unsigned long long)) { flags |= (FL_LONG | FL_REPD_TYPE); } else #endif if (sizeof(void *) == sizeof(unsigned long)) { flags |= FL_LONG; } } #ifdef CONFIG_LIBC_NUMBERED_ARGS if ((flags & FL_ARGNUMBER) != 0) { if (argnumber > 0 && argnumber <= numargs) { if (stream == NULL) { if ((c >= 'E' && c <= 'G') || (c >= 'e' && c <= 'g')) { arglist[argnumber-1].type = TYPE_DOUBLE; } else if (c == 'i' || c == 'd' || c == 'u' || c == 'p') { if ((flags & FL_LONG) == 0) { arglist[argnumber-1].type = TYPE_INT; } else if ((flags & FL_REPD_TYPE) == 0) { arglist[argnumber-1].type = TYPE_LONG; } else { arglist[argnumber-1].type = TYPE_LONG_LONG; } } else if (c == 'c') { arglist[argnumber-1].type = TYPE_INT; } else if (c == 's') { arglist[argnumber-1].type = TYPE_CHAR_POINTER; } if (argnumber > total_len) { total_len = argnumber; } continue; /* We do only parsing */ } } else { goto ret; } } else if (stream == NULL) { continue; /* We do only parsing */ } #endif #ifdef CONFIG_LIBC_FLOATINGPOINT if (c >= 'E' && c <= 'G') { flags |= FL_FLTUPP; c += 'e' - 'E'; goto flt_oper; } else if (c >= 'e' && c <= 'g') { double value; int exp; /* Exponent of master decimal digit */ int n; uint8_t sign; /* Sign character (or 0) */ uint8_t ndigs; /* Number of digits to convert */ uint8_t ndecimal; /* Digits after decimal (for 'f' format), 0 if * no limit */ flags &= ~FL_FLTUPP; flt_oper: ndigs = 0; if ((flags & FL_PREC) == 0) { prec = 6; } flags &= ~(FL_FLTEXP | FL_FLTFIX); if (c == 'e') { ndigs = prec + 1; ndecimal = 0; flags |= FL_FLTEXP; } else if (c == 'f') { ndigs = DTOA_MAX_DIG; ndecimal = prec; flags |= FL_FLTFIX; } else { ndigs = prec; ndecimal = 0; } if (ndigs > DTOA_MAX_DIG) { ndigs = DTOA_MAX_DIG; } #ifdef CONFIG_LIBC_NUMBERED_ARGS if ((flags & FL_ARGNUMBER) != 0) { value = arglist[argnumber-1].value.d; } else { value = va_arg(ap, double); } #else value = va_arg(ap, double); #endif ndigs = __dtoa_engine(value, &_dtoa, ndigs, ndecimal); exp = _dtoa.exp; sign = 0; if ((_dtoa.flags & DTOA_MINUS) && !(_dtoa.flags & DTOA_NAN)) { sign = '-'; } else if ((flags & FL_PLUS) != 0) { sign = '+'; } else if ((flags & FL_SPACE) != 0) { sign = ' '; } if (_dtoa.flags & (DTOA_NAN | DTOA_INF)) { FAR const char *p; ndigs = sign ? 4 : 3; if (width > ndigs) { width -= ndigs; if ((flags & FL_LPAD) == 0) { do { putc(' ', stream); } while (--width); } } else { width = 0; } if (sign) { putc(sign, stream); } p = "inf"; if (_dtoa.flags & DTOA_NAN) { p = "nan"; } # if ('I'-'i' != 'N'-'n') || ('I'-'i' != 'F'-'f') || ('I'-'i' != 'A'-'a') # error # endif while ((ndigs = *p) != 0) { if ((flags & FL_FLTUPP) != 0) { ndigs += 'I' - 'i'; } putc(ndigs, stream); p++; } goto tail; } if ((flags & (FL_FLTEXP | FL_FLTFIX)) == 0) { /* 'g(G)' format */ prec = ndigs; /* Remove trailing zeros */ while (ndigs > 0 && _dtoa.digits[ndigs - 1] == '0') { ndigs--; } if (-4 <= exp && exp < prec) { flags |= FL_FLTFIX; if (exp < 0 || ndigs > exp) { prec = ndigs - (exp + 1); } else { prec = 0; } } else { /* Limit displayed precision to available precision */ prec = ndigs - 1; } } /* Conversion result length, width := free space length */ if ((flags & FL_FLTFIX) != 0) { n = (exp > 0 ? exp + 1 : 1); } else { n = 5; /* 1e+00 */ } if (sign != 0) { n += 1; } if (prec != 0) { n += prec + 1; } else if ((flags & FL_ALT) != 0) { n += 1; } width = width > n ? width - n : 0; /* Output before first digit */ if ((flags & (FL_LPAD | FL_ZFILL)) == 0) { while (width) { putc(' ', stream); width--; } } if (sign != 0) { putc(sign, stream); } if ((flags & FL_LPAD) == 0) { while (width) { putc('0', stream); width--; } } if ((flags & FL_FLTFIX) != 0) { /* 'f' format */ char out; /* At this point, we should have exp exponent of leftmost digit * in _dtoa.digits ndigs number of buffer digits to print prec * number of digits after decimal In the loop, 'n' walks over * the exponent value */ n = exp > 0 ? exp : 0; /* Exponent of left digit */ do { /* Insert decimal point at correct place */ if (n == -1) { putc('.', stream); } /* Pull digits from buffer when in-range, otherwise use 0 */ if (0 <= exp - n && exp - n < ndigs) { out = _dtoa.digits[exp - n]; } else { out = '0'; } if (--n < -prec) { if ((flags & FL_ALT) != 0 && n == -1) { putc('.', stream); } break; } putc(out, stream); } while (1); if (n == exp && (_dtoa.digits[0] > '5' || (_dtoa.digits[0] == '5' && !(_dtoa.flags & DTOA_CARRY)))) { out = '1'; } putc(out, stream); } else { /* 'e(E)' format * * Mantissa */ if (_dtoa.digits[0] != '1') { _dtoa.flags &= ~DTOA_CARRY; } putc(_dtoa.digits[0], stream); if (prec > 0) { uint8_t pos; putc('.', stream); for (pos = 1; pos < 1 + prec; pos++) { putc(pos < ndigs ? _dtoa.digits[pos] : '0', stream); } } else if ((flags & FL_ALT) != 0) { putc('.', stream); } /* Exponent */ putc(flags & FL_FLTUPP ? 'E' : 'e', stream); ndigs = '+'; if (exp < 0 || (exp == 0 && (_dtoa.flags & DTOA_CARRY) != 0)) { exp = -exp; ndigs = '-'; } putc(ndigs, stream); for (ndigs = '0'; exp >= 10; exp -= 10) { ndigs += 1; } putc(ndigs, stream); putc('0' + exp, stream); } goto tail; } #else /* !CONFIG_LIBC_FLOATINGPOINT */ if ((c >= 'E' && c <= 'G') || (c >= 'e' && c <= 'g')) { (void)va_arg(ap, double); pnt = "*float*"; size = sizeof("*float*") - 1; goto str_lpad; } #endif switch (c) { case 'c': #ifdef CONFIG_LIBC_NUMBERED_ARGS if ((flags & FL_ARGNUMBER) != 0) { buf[0] = (int)arglist[argnumber-1].value.u; } else { buf[0] = va_arg(ap, int); } #else buf[0] = va_arg(ap, int); #endif pnt = (FAR char *) buf; size = 1; goto str_lpad; case 's': case 'S': #ifdef CONFIG_LIBC_NUMBERED_ARGS if ((flags & FL_ARGNUMBER) != 0) { pnt = (FAR char *)arglist[argnumber-1].value.cp; } else { pnt = va_arg(ap, FAR char *); } #else pnt = va_arg(ap, FAR char *); #endif if (pnt == NULL) { pnt = g_nullstring; } size = strnlen(pnt, (flags & FL_PREC) ? prec : ~0); str_lpad: if ((flags & FL_LPAD) == 0) { while (size < width) { putc(' ', stream); width--; } } while (size) { putc(*pnt++, stream); if (width != 0) { width -= 1; } size -= 1; } goto tail; } if (c == 'd' || c == 'i') { #ifndef CONFIG_LIBC_LONG_LONG long x; #else long long x; if ((flags & FL_LONG) != 0 && (flags & FL_REPD_TYPE) != 0) { #ifdef CONFIG_LIBC_NUMBERED_ARGS if ((flags & FL_ARGNUMBER) != 0) { x = (long long)arglist[argnumber-1].value.ull; } else { x = va_arg(ap, long long); } #else x = va_arg(ap, long long); #endif } else #endif if ((flags & FL_LONG) != 0) { #ifdef CONFIG_LIBC_NUMBERED_ARGS if ((flags & FL_ARGNUMBER) != 0) { x = (long)arglist[argnumber-1].value.ul; } else { x = va_arg(ap, long); } #else x = va_arg(ap, long); #endif } else { #ifdef CONFIG_LIBC_NUMBERED_ARGS if ((flags & FL_ARGNUMBER) != 0) { x = (int)arglist[argnumber-1].value.u; } else { x = va_arg(ap, int); } #else x = va_arg(ap, int); #endif if ((flags & FL_SHORT) != 0) { if ((flags & FL_REPD_TYPE) == 0) { x = (short)x; } else { x = (signed char)x; } } } flags &= ~(FL_NEGATIVE | FL_ALT); if (x < 0) { x = -x; flags |= FL_NEGATIVE; } if ((flags & FL_PREC) != 0 && prec == 0 && x == 0) { c = 0; } else { c = __ultoa_invert(x, (FAR char *)buf, 10) - (FAR char *)buf; } } else { int base; #ifndef CONFIG_LIBC_LONG_LONG unsigned long x; #else unsigned long long x; if ((flags & FL_LONG) != 0 && (flags & FL_REPD_TYPE) != 0) { #ifdef CONFIG_LIBC_NUMBERED_ARGS if ((flags & FL_ARGNUMBER) != 0) { x = arglist[argnumber-1].value.ull; } else { x = va_arg(ap, unsigned long long); } #else x = va_arg(ap, unsigned long long); #endif } else #endif if ((flags & FL_LONG) != 0) { #ifdef CONFIG_LIBC_NUMBERED_ARGS if ((flags & FL_ARGNUMBER) != 0) { x = arglist[argnumber-1].value.ul; } else { x = va_arg(ap, unsigned long); } #else x = va_arg(ap, unsigned long); #endif } else { #ifdef CONFIG_LIBC_NUMBERED_ARGS if ((flags & FL_ARGNUMBER) != 0) { x = (unsigned int)arglist[argnumber-1].value.u; } else { x = va_arg(ap, unsigned int); } #else x = va_arg(ap, unsigned int); #endif if ((flags & FL_SHORT) != 0) { if ((flags & FL_REPD_TYPE) == 0) { x = (unsigned short)x; } else { x = (unsigned char)x; } } } flags &= ~(FL_PLUS | FL_SPACE); switch (c) { case 'u': flags &= ~FL_ALT; base = 10; break; case 'o': base = 8; break; case 'p': flags |= FL_ALT; /* no break */ case 'x': if ((flags & FL_ALT) != 0) { flags |= FL_ALTHEX; } base = 16; break; case 'X': if ((flags & FL_ALT) != 0) { flags |= (FL_ALTHEX | FL_ALTUPP); } base = 16 | XTOA_UPPER; break; default: putc('%', stream); putc(c, stream); continue; } if ((flags & FL_PREC) != 0 && prec == 0 && x == 0) { c = 0; } else { c = __ultoa_invert(x, (FAR char *)buf, base) - (FAR char *)buf; } flags &= ~FL_NEGATIVE; } len = c; if ((flags & FL_PREC) != 0) { flags &= ~FL_ZFILL; if (len < prec) { len = prec; if ((flags & FL_ALT) != 0 && (flags & FL_ALTHEX) == 0) { flags &= ~FL_ALT; } } } if ((flags & FL_ALT) != 0) { if (buf[c - 1] == '0') { flags &= ~(FL_ALT | FL_ALTHEX | FL_ALTUPP); } else { len += 1; if ((flags & FL_ALTHEX) != 0) { len += 1; } } } else if ((flags & (FL_NEGATIVE | FL_PLUS | FL_SPACE)) != 0) { len += 1; } if ((flags & FL_LPAD) == 0) { if ((flags & FL_ZFILL) != 0) { prec = c; if (len < width) { prec += width - len; len = width; } } while (len < width) { putc(' ', stream); len++; } } width = (len < width) ? width - len : 0; if ((flags & FL_ALT) != 0) { putc('0', stream); if ((flags & FL_ALTHEX) != 0) { putc(flags & FL_ALTUPP ? 'X' : 'x', stream); } } else if ((flags & (FL_NEGATIVE | FL_PLUS | FL_SPACE)) != 0) { unsigned char z = ' '; if ((flags & FL_PLUS) != 0) { z = '+'; } if ((flags & FL_NEGATIVE) != 0) { z = '-'; } putc(z, stream); } while (prec > c) { putc('0', stream); prec--; } while (c) { putc(buf[--c], stream); } tail: /* Tail is possible. */ while (width) { putc(' ', stream); width--; } } ret: return total_len; } /**************************************************************************** * Public Functions ****************************************************************************/ int lib_vsprintf(FAR struct lib_outstream_s *stream, FAR const IPTR char *fmt, va_list ap) { #ifdef CONFIG_LIBC_NUMBERED_ARGS int i; struct arg arglist[NL_ARGMAX]; int numargs; /* We do 2 passes of parsing and fill the arglist between the passes. */ numargs = vsprintf_internal(NULL, arglist, NL_ARGMAX, fmt, ap); for (i = 0; i < numargs; i++) { switch (arglist[i].type) { case TYPE_LONG_LONG: #ifdef CONFIG_LIBC_LONG_LONG arglist[i].value.ull = va_arg(ap, unsigned long long); break; #endif case TYPE_LONG: arglist[i].value.ul = va_arg(ap, unsigned long); break; case TYPE_INT: arglist[i].value.u = va_arg(ap, unsigned int); break; case TYPE_DOUBLE: arglist[i].value.d = va_arg(ap, double); break; case TYPE_CHAR_POINTER: arglist[i].value.cp = va_arg(ap, FAR char *); break; } } return vsprintf_internal(stream, arglist, numargs, fmt, ap); #else return vsprintf_internal(stream, NULL, 0, fmt, ap); #endif }