| /* 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 <http://www.gnu.org/licenses/>. | |
| */ | |
| /* 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); | |
| } |