blob: 6f1758f2c4c4ad45b196f71feb8f3d2878d31977 [file] [log] [blame]
/* 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);
}
}