| /* GNU m4 -- A simple macro processor |
| |
| Copyright (C) 1989-1994, 2006-2014, 2016-2017, 2020-2021 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 <https://www.gnu.org/licenses/>. |
| */ |
| |
| /* printf like formatting for m4. */ |
| |
| #include "m4.h" |
| #include "xvasprintf.h" |
| |
| /* Simple varargs substitute. We assume int and unsigned int are the |
| same size; likewise for long and unsigned long. */ |
| |
| /* Parse STR as an integer, reporting warnings. */ |
| static int |
| arg_int (const char *str) |
| { |
| char *endp; |
| long value; |
| size_t len = strlen (str); |
| |
| if (!len) |
| { |
| M4ERROR ((warning_status, 0, _("empty string treated as 0"))); |
| return 0; |
| } |
| errno = 0; |
| value = strtol (str, &endp, 10); |
| if (endp - str - len) |
| M4ERROR ((warning_status, 0, _("non-numeric argument %s"), str)); |
| else if (c_isspace (*str)) |
| M4ERROR ((warning_status, 0, _("leading whitespace ignored"))); |
| else if (errno == ERANGE || (int) value != value) |
| M4ERROR ((warning_status, 0, _("numeric overflow detected"))); |
| return value; |
| } |
| |
| /* Parse STR as a long, reporting warnings. */ |
| static long |
| arg_long (const char *str) |
| { |
| char *endp; |
| long value; |
| size_t len = strlen (str); |
| |
| if (!len) |
| { |
| M4ERROR ((warning_status, 0, _("empty string treated as 0"))); |
| return 0L; |
| } |
| errno = 0; |
| value = strtol (str, &endp, 10); |
| if (endp - str - len) |
| M4ERROR ((warning_status, 0, _("non-numeric argument %s"), str)); |
| else if (c_isspace (*str)) |
| M4ERROR ((warning_status, 0, _("leading whitespace ignored"))); |
| else if (errno == ERANGE) |
| M4ERROR ((warning_status, 0, _("numeric overflow detected"))); |
| return value; |
| } |
| |
| /* Parse STR as a double, reporting warnings. */ |
| static double |
| arg_double (const char *str) |
| { |
| char *endp; |
| double value; |
| size_t len = strlen (str); |
| |
| if (!len) |
| { |
| M4ERROR ((warning_status, 0, _("empty string treated as 0"))); |
| return 0.0; |
| } |
| errno = 0; |
| value = strtod (str, &endp); |
| if (endp - str - len) |
| M4ERROR ((warning_status, 0, _("non-numeric argument %s"), str)); |
| else if (c_isspace (*str)) |
| M4ERROR ((warning_status, 0, _("leading whitespace ignored"))); |
| else if (errno == ERANGE) |
| M4ERROR ((warning_status, 0, _("numeric overflow detected"))); |
| return value; |
| } |
| |
| #define ARG_INT(argc, argv) \ |
| ((argc == 0) ? 0 : \ |
| (--argc, argv++, arg_int (TOKEN_DATA_TEXT (argv[-1])))) |
| |
| #define ARG_LONG(argc, argv) \ |
| ((argc == 0) ? 0 : \ |
| (--argc, argv++, arg_long (TOKEN_DATA_TEXT (argv[-1])))) |
| |
| #define ARG_STR(argc, argv) \ |
| ((argc == 0) ? "" : \ |
| (--argc, argv++, TOKEN_DATA_TEXT (argv[-1]))) |
| |
| #define ARG_DOUBLE(argc, argv) \ |
| ((argc == 0) ? 0 : \ |
| (--argc, argv++, arg_double (TOKEN_DATA_TEXT (argv[-1])))) |
| |
| |
| /*------------------------------------------------------------------. |
| | 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. | |
| `------------------------------------------------------------------*/ |
| |
| void |
| expand_format (struct obstack *obs, int argc, token_data **argv) |
| { |
| const char *f; /* format control string */ |
| const char *fmt; /* position within f */ |
| char fstart[] = "%'+- 0#*.*hhd"; /* current format spec */ |
| char *p; /* position within fstart */ |
| unsigned char c; /* a simple character */ |
| |
| /* 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]; |
| |
| /* Buffer and stuff. */ |
| char *str; /* malloc'd buffer of formatted text */ |
| enum {CHAR, INT, LONG, DOUBLE, STR} datatype; |
| |
| f = fmt = ARG_STR (argc, argv); |
| memset (ok, 0, sizeof ok); |
| while (1) |
| { |
| const char *percent = strchr (fmt, '%'); |
| if (!percent) |
| { |
| obstack_grow (obs, fmt, strlen (fmt)); |
| return; |
| } |
| obstack_grow (obs, fmt, percent - fmt); |
| fmt = percent + 1; |
| |
| if (*fmt == '%') |
| { |
| obstack_1grow (obs, '%'); |
| fmt++; |
| 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) && 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 (argc, argv); |
| fmt++; |
| } |
| else |
| while (c_isdigit (*fmt)) |
| { |
| width = 10 * width + *fmt - '0'; |
| fmt++; |
| } |
| |
| /* 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; |
| if (*(++fmt) == '*') |
| { |
| prec = ARG_INT (argc, argv); |
| ++fmt; |
| } |
| else |
| { |
| prec = 0; |
| while (c_isdigit (*fmt)) |
| { |
| prec = 10 * prec + *fmt - '0'; |
| fmt++; |
| } |
| } |
| } |
| |
| /* Length modifiers. We don't yet recognize ll, j, t, or z. */ |
| if (*fmt == 'l') |
| { |
| *p++ = 'l'; |
| lflag = 1; |
| fmt++; |
| ok['c'] = ok['s'] = 0; |
| } |
| else if (*fmt == 'h') |
| { |
| *p++ = 'h'; |
| fmt++; |
| if (*fmt == 'h') |
| { |
| *p++ = 'h'; |
| fmt++; |
| } |
| 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]) |
| { |
| M4ERROR ((warning_status, 0, |
| _("Warning: unrecognized specifier in `%s'"), f)); |
| if (c == '\0') |
| fmt--; |
| continue; |
| } |
| |
| /* 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'; |
| |
| /* Our constructed format string in fstart is safe. */ |
| #if 4 < __GNUC__ + (6 <= __GNUC_MINOR__) |
| # pragma GCC diagnostic push |
| # pragma GCC diagnostic ignored "-Wformat-nonliteral" |
| #endif |
| |
| switch (datatype) |
| { |
| case CHAR: |
| str = xasprintf (fstart, width, ARG_INT(argc, argv)); |
| break; |
| |
| case INT: |
| str = xasprintf (fstart, width, prec, ARG_INT(argc, argv)); |
| break; |
| |
| case LONG: |
| str = xasprintf (fstart, width, prec, ARG_LONG(argc, argv)); |
| break; |
| |
| case DOUBLE: |
| str = xasprintf (fstart, width, prec, ARG_DOUBLE(argc, argv)); |
| break; |
| |
| case STR: |
| str = xasprintf (fstart, width, prec, ARG_STR(argc, argv)); |
| break; |
| |
| default: |
| abort(); |
| } |
| #if 4 < __GNUC__ + (6 <= __GNUC_MINOR__) |
| # pragma GCC diagnostic pop |
| #endif |
| |
| /* NULL was returned on failure, such as invalid format string. For |
| now, just silently ignore that bad specifier. */ |
| if (str == NULL) |
| continue; |
| |
| obstack_grow (obs, str, strlen (str)); |
| free (str); |
| } |
| } |