summaryrefslogtreecommitdiff
path: root/src/format.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/format.c')
-rw-r--r--src/format.c385
1 files changed, 385 insertions, 0 deletions
diff --git a/src/format.c b/src/format.c
new file mode 100644
index 0000000..43a23cd
--- /dev/null
+++ b/src/format.c
@@ -0,0 +1,385 @@
+/* GNU m4 -- A simple macro processor
+
+ Copyright (C) 1989-1994, 2006-2011 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 "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 (isspace (to_uchar (*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 (isspace (to_uchar (*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 (isspace (to_uchar (*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 (isdigit (to_uchar (*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 (isdigit (to_uchar (*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';
+
+ 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();
+ }
+
+ /* 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);
+ }
+}