/* GNU m4 -- A simple macro processor Copyright (C) 1989-1994, 2001, 2006-2010, 2013-2014 Free Software Foundation, Inc. This file is part of GNU M4. GNU M4 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 3 of the License, or (at your option) any later version. GNU M4 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, see . */ /* printf like formatting for m4. */ #include "vasnprintf.h" /* Simple varargs substitute. We assume int and unsigned int are the same size; likewise for long and unsigned long. We do not yet handle long double or long long. */ /* Parse STR of length LEN as an integer, reporting warnings on behalf of ME. */ static int arg_int (struct m4 *context, const m4_call_info *me, const char *str, size_t len) { char *endp; long value; /* TODO - also allow parsing `'a' or `"a' which results in the numeric value of 'a', as in printf(1). */ if (!len) { m4_warn (context, 0, me, _("empty string treated as 0")); return 0; } errno = 0; value = strtol (str, &endp, 10); if (endp - str != len) m4_warn (context, 0, me, _("non-numeric argument %s"), quotearg_style_mem (locale_quoting_style, str, len)); else if (isspace (to_uchar (*str))) m4_warn (context, 0, me, _("leading whitespace ignored")); else if (errno == ERANGE || (int) value != value) m4_warn (context, 0, me, _("numeric overflow detected")); return value; } /* Parse STR of length LEN as a long, reporting warnings on behalf of ME. */ static long arg_long (struct m4 *context, const m4_call_info *me, const char *str, size_t len) { char *endp; long value; /* TODO - also allow parsing `'a' or `"a' which results in the numeric value of 'a', as in printf(1). */ if (!len) { m4_warn (context, 0, me, _("empty string treated as 0")); return 0L; } errno = 0; value = strtol (str, &endp, 10); if (endp - str != len) m4_warn (context, 0, me, _("non-numeric argument %s"), quotearg_style_mem (locale_quoting_style, str, len)); else if (isspace (to_uchar (*str))) m4_warn (context, 0, me, _("leading whitespace ignored")); else if (errno == ERANGE) m4_warn (context, 0, me, _("numeric overflow detected")); return value; } /* Check STR of length LEN for embedded NUL, reporting warnings on behalf of ME. */ static const char * arg_string (struct m4 *context, const m4_call_info *me, const char *str, size_t len) { if (strlen (str) < len) m4_warn (context, 0, me, _("argument %s truncated"), quotearg_style_mem (locale_quoting_style, str, len)); return str; } /* Parse STR of length LEN as a double, reporting warnings on behalf of ME. */ static double arg_double (struct m4 *context, const m4_call_info *me, const char *str, size_t len) { char *endp; double value; if (!len) { m4_warn (context, 0, me, _("empty string treated as 0")); return 0.0; } errno = 0; value = strtod (str, &endp); if (endp - str != len) m4_warn (context, 0, me, _("non-numeric argument %s"), quotearg_style_mem (locale_quoting_style, str, len)); else if (isspace (to_uchar (*str))) m4_warn (context, 0, me, _("leading whitespace ignored")); else if (errno == ERANGE) m4_warn (context, 0, me, _("numeric overflow detected")); return value; } #define ARG_INT(i, argc, argv) \ ((argc <= ++i) ? 0 : arg_int (context, me, M4ARG (i), M4ARGLEN (i))) #define ARG_LONG(i, argc, argv) \ ((argc <= ++i) ? 0L : arg_long (context, me, M4ARG (i), M4ARGLEN (i))) #define ARG_STR(i, argc, argv) \ ((argc <= ++i) ? "" : arg_string (context, me, M4ARG (i), M4ARGLEN (i))) #define ARG_DOUBLE(i, argc, argv) \ ((argc <= ++i) ? 0.0 : arg_double (context, me, M4ARG (i), M4ARGLEN (i))) /* The main formatting function. Output is placed on the obstack OBS, the first argument in ARGV is the formatting string, and the rest is arguments for the string. Warn rather than invoke unspecified behavior in the underlying printf when we do not recognize a format. */ static void format (m4 *context, m4_obstack *obs, int argc, m4_macro_args *argv) { const m4_call_info *me = m4_arg_info (argv); const char *f; /* Format control string. */ size_t f_len; /* Length of f. */ const char *fmt; /* Position within f. */ char fstart[] = "%'+- 0#*.*hhd"; /* Current format spec. */ char *p; /* Position within fstart. */ unsigned char c; /* A simple character. */ int i = 1; /* Index within argc used so far. */ bool valid_format = true; /* True if entire format string ok. */ /* Flags. */ char flags; /* Flags to use in fstart. */ enum { THOUSANDS = 0x01, /* '\''. */ PLUS = 0x02, /* '+'. */ MINUS = 0x04, /* '-'. */ SPACE = 0x08, /* ' '. */ ZERO = 0x10, /* '0'. */ ALT = 0x20, /* '#'. */ DONE = 0x40 /* No more flags. */ }; /* Precision specifiers. */ int width; /* Minimum field width. */ int prec; /* Precision. */ char lflag; /* Long flag. */ /* Specifiers we are willing to accept. ok['x'] implies %x is ok. Various modifiers reduce the set, in order to avoid undefined behavior in printf. */ char ok[128]; /* Check that formatted text succeeded with correct type. */ int result = 0; enum {CHAR, INT, LONG, DOUBLE, STR} datatype; f = fmt = M4ARG (1); f_len = M4ARGLEN (1); assert (!f[f_len]); /* Requiring a terminating NUL makes parsing simpler. */ memset (ok, 0, sizeof ok); while (1) { const char *percent = (char *) memchr (fmt, '%', f_len); if (!percent) { obstack_grow (obs, fmt, f_len); break; } obstack_grow (obs, fmt, percent - fmt); f_len -= percent - fmt + 1; fmt = percent + 1; if (*fmt == '%') { obstack_1grow (obs, '%'); fmt++; f_len--; continue; } p = fstart + 1; /* % */ lflag = 0; ok['a'] = ok['A'] = ok['c'] = ok['d'] = ok['e'] = ok['E'] = ok['f'] = ok['F'] = ok['g'] = ok['G'] = ok['i'] = ok['o'] = ok['s'] = ok['u'] = ok['x'] = ok['X'] = 1; /* Parse flags. */ flags = 0; do { switch (*fmt) { case '\'': /* thousands separator */ ok['a'] = ok['A'] = ok['c'] = ok['e'] = ok['E'] = ok['o'] = ok['s'] = ok['x'] = ok['X'] = 0; flags |= THOUSANDS; break; case '+': /* mandatory sign */ ok['c'] = ok['o'] = ok['s'] = ok['u'] = ok['x'] = ok['X'] = 0; flags |= PLUS; break; case ' ': /* space instead of positive sign */ ok['c'] = ok['o'] = ok['s'] = ok['u'] = ok['x'] = ok['X'] = 0; flags |= SPACE; break; case '0': /* zero padding */ ok['c'] = ok['s'] = 0; flags |= ZERO; break; case '#': /* alternate output */ ok['c'] = ok['d'] = ok['i'] = ok['s'] = ok['u'] = 0; flags |= ALT; break; case '-': /* left justification */ flags |= MINUS; break; default: flags |= DONE; break; } } while (!(flags & DONE) && (f_len--, fmt++)); if (flags & THOUSANDS) *p++ = '\''; if (flags & PLUS) *p++ = '+'; if (flags & MINUS) *p++ = '-'; if (flags & SPACE) *p++ = ' '; if (flags & ZERO) *p++ = '0'; if (flags & ALT) *p++ = '#'; /* Minimum field width; an explicit 0 is the same as not giving the width. */ width = 0; *p++ = '*'; if (*fmt == '*') { width = ARG_INT (i, argc, argv); fmt++; f_len--; } else while (isdigit ((unsigned char) *fmt)) { width = 10 * width + *fmt - '0'; fmt++; f_len--; } /* Maximum precision; an explicit negative precision is the same as not giving the precision. A lone '.' is a precision of 0. */ prec = -1; *p++ = '.'; *p++ = '*'; if (*fmt == '.') { ok['c'] = 0; f_len--; if (*(++fmt) == '*') { prec = ARG_INT (i, argc, argv); ++fmt; f_len--; } else { prec = 0; while (isdigit ((unsigned char) *fmt)) { prec = 10 * prec + *fmt - '0'; fmt++; f_len--; } } } /* Length modifiers. We don't yet recognize ll, j, t, or z. */ if (*fmt == 'l') { *p++ = 'l'; lflag = 1; fmt++; f_len--; ok['c'] = ok['s'] = 0; } else if (*fmt == 'h') { *p++ = 'h'; fmt++; f_len--; if (*fmt == 'h') { *p++ = 'h'; fmt++; f_len--; } ok['a'] = ok['A'] = ok['c'] = ok['e'] = ok['E'] = ok['f'] = ok['F'] = ok['g'] = ok['G'] = ok['s'] = 0; } c = *fmt; if (sizeof ok <= c || !ok[c] || !f_len) { m4_warn (context, 0, me, _("unrecognized specifier in %s"), quotearg_style_mem (locale_quoting_style, f, M4ARGLEN (1))); valid_format = false; if (f_len > 0) { fmt++; f_len--; } continue; } fmt++; f_len--; /* Specifiers. We don't yet recognize C, S, n, or p. */ switch (c) { case 'c': datatype = CHAR; p -= 2; /* %.*c is undefined, so undo the '.*'. */ break; case 's': datatype = STR; break; case 'd': case 'i': case 'o': case 'x': case 'X': case 'u': datatype = lflag ? LONG : INT; break; case 'a': case 'A': case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': datatype = DOUBLE; break; default: abort (); } *p++ = c; *p = '\0'; switch (datatype) { case CHAR: result = obstack_printf (obs, fstart, width, ARG_INT (i, argc, argv)); break; case INT: result = obstack_printf (obs, fstart, width, prec, ARG_INT (i, argc, argv)); break; case LONG: result = obstack_printf (obs, fstart, width, prec, ARG_LONG (i, argc, argv)); break; case DOUBLE: result = obstack_printf (obs, fstart, width, prec, ARG_DOUBLE (i, argc, argv)); break; case STR: result = obstack_printf (obs, fstart, width, prec, ARG_STR (i, argc, argv)); break; default: abort (); } /* Since obstack_printf can only fail with EILSEQ or EINVAL, but we constructed fstart, the result should not be negative. */ assert (0 <= result); } if (valid_format) m4_bad_argc (context, argc, me, i, i, true); }