397 lines
11 KiB
C
397 lines
11 KiB
C
/* GNU m4 -- A simple macro processor
|
|
|
|
Copyright (C) 1989-1994, 2006-2014, 2016-2017, 2020-2026 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"
|
|
|
|
/* 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 _GL_GNUC_PREREQ (4, 3) || defined __clang__
|
|
# 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 _GL_GNUC_PREREQ (4, 3) || defined __clang__
|
|
# 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);
|
|
}
|
|
}
|