mirror of
https://https.git.savannah.gnu.org/git/m4.git
synced 2026-01-28 02:15:00 +00:00
* m4/output.c (threshold_diversion_CB): s/div/diversion/. * m4/macro.c (make_argv_ref, arg_symbol, m4_arg_symbol) (m4_is_arg_text, m4_is_arg_func, m4_arg_text, m4_arg_empty) (m4_arg_len, m4_arg_func, m4__arg_print, m4_make_argv_ref) (m4_push_arg, m4__push_arg_quote): s/index/arg/. * modules/format.c (format): Likewise. * modules/m4.c (ifelse): Likewise. Signed-off-by: Eric Blake <ebb9@byu.net>
405 lines
9.9 KiB
C
405 lines
9.9 KiB
C
/* GNU m4 -- A simple macro processor
|
||
Copyright (C) 1989, 1990, 1991, 1992, 1993, 1994, 2001, 2006, 2007,
|
||
2008 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 as an integer, reporting warnings on behalf of ME. */
|
||
static int
|
||
arg_int (struct m4 *context, const char *me, const char *str)
|
||
{
|
||
char *endp;
|
||
long value;
|
||
|
||
/* TODO - also allow parsing `'a' or `"a' which results in the
|
||
numeric value of 'a', as in printf(1). */
|
||
if (*str == '\0')
|
||
{
|
||
m4_warn (context, 0, me, _("empty string treated as 0"));
|
||
return 0;
|
||
}
|
||
errno = 0;
|
||
value = strtol (str, &endp, 10);
|
||
if (*endp != '\0')
|
||
m4_warn (context, 0, me, _("non-numeric argument `%s'"), str);
|
||
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 as a long, reporting warnings on behalf of ME. */
|
||
static long
|
||
arg_long (struct m4 *context, const char *me, const char *str)
|
||
{
|
||
char *endp;
|
||
long value;
|
||
|
||
/* TODO - also allow parsing `'a' or `"a' which results in the
|
||
numeric value of 'a', as in printf(1). */
|
||
if (*str == '\0')
|
||
{
|
||
m4_warn (context, 0, me, _("empty string treated as 0"));
|
||
return 0L;
|
||
}
|
||
errno = 0;
|
||
value = strtol (str, &endp, 10);
|
||
if (*endp != '\0')
|
||
m4_warn (context, 0, me, _("non-numeric argument `%s'"), str);
|
||
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;
|
||
}
|
||
|
||
/* Parse STR as a double, reporting warnings on behalf of ME. */
|
||
static double
|
||
arg_double (struct m4 *context, const char *me, const char *str)
|
||
{
|
||
char *endp;
|
||
double value;
|
||
|
||
if (*str == '\0')
|
||
{
|
||
m4_warn (context, 0, me, _("empty string treated as 0"));
|
||
return 0.0;
|
||
}
|
||
errno = 0;
|
||
value = strtod (str, &endp);
|
||
if (*endp != '\0')
|
||
m4_warn (context, 0, me, _("non-numeric argument `%s'"), str);
|
||
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)))
|
||
|
||
#define ARG_LONG(i, argc, argv) \
|
||
((argc <= ++i) ? 0L : arg_long (context, me, M4ARG (i)))
|
||
|
||
#define ARG_STR(i, argc, argv) \
|
||
((argc <= ++i) ? "" : M4ARG (i))
|
||
|
||
#define ARG_DOUBLE(i, argc, argv) \
|
||
((argc <= ++i) ? 0.0 : arg_double (context, me, M4ARG (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 char *me = M4ARG (0); /* Macro name. */
|
||
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. */
|
||
int i = 0; /* 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];
|
||
|
||
/* Buffer and stuff. */
|
||
char *base; /* Current position in obs. */
|
||
size_t len; /* Length of formatted text. */
|
||
char *str; /* Malloc'd buffer of formatted text. */
|
||
enum {CHAR, INT, LONG, DOUBLE, STR} datatype;
|
||
|
||
f = fmt = ARG_STR (i, argc, argv);
|
||
memset (ok, 0, sizeof ok);
|
||
while (true)
|
||
{
|
||
while ((c = *fmt++) != '%')
|
||
{
|
||
if (c == '\0')
|
||
{
|
||
if (valid_format)
|
||
m4_bad_argc (context, argc, me, i, i, true);
|
||
return;
|
||
}
|
||
obstack_1grow (obs, c);
|
||
}
|
||
|
||
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 (i, argc, argv);
|
||
fmt++;
|
||
}
|
||
else
|
||
while (isdigit ((unsigned char) *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 (i, argc, argv);
|
||
++fmt;
|
||
}
|
||
else
|
||
{
|
||
prec = 0;
|
||
while (isdigit ((unsigned char) *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 (c > sizeof ok || !ok[c])
|
||
{
|
||
m4_warn (context, 0, me, _("unrecognized specifier in `%s'"), f);
|
||
valid_format = false;
|
||
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';
|
||
base = obstack_next_free (obs);
|
||
len = obstack_room (obs);
|
||
|
||
switch (datatype)
|
||
{
|
||
case CHAR:
|
||
str = asnprintf (base, &len, fstart, width,
|
||
ARG_INT (i, argc, argv));
|
||
break;
|
||
|
||
case INT:
|
||
str = asnprintf (base, &len, fstart, width, prec,
|
||
ARG_INT (i, argc, argv));
|
||
break;
|
||
|
||
case LONG:
|
||
str = asnprintf (base, &len, fstart, width, prec,
|
||
ARG_LONG (i, argc, argv));
|
||
break;
|
||
|
||
case DOUBLE:
|
||
str = asnprintf (base, &len, fstart, width, prec,
|
||
ARG_DOUBLE (i, argc, argv));
|
||
break;
|
||
|
||
case STR:
|
||
str = asnprintf (base, &len, fstart, width, prec,
|
||
ARG_STR (i, argc, argv));
|
||
break;
|
||
|
||
default:
|
||
abort ();
|
||
}
|
||
|
||
if (str == NULL)
|
||
/* NULL is unexpected (EILSEQ and EINVAL are not possible
|
||
based on our construction of fstart, leaving only ENOMEM,
|
||
which should always be fatal). */
|
||
m4_error (context, EXIT_FAILURE, errno, me,
|
||
_("unable to format output for `%s'"), f);
|
||
else if (str == base)
|
||
/* The output was already computed in place, but we need to
|
||
account for its size. */
|
||
obstack_blank_fast (obs, len);
|
||
else
|
||
{
|
||
/* The output exceeded available obstack space, copy the
|
||
allocated string. */
|
||
obstack_grow (obs, str, len);
|
||
free (str);
|
||
}
|
||
}
|
||
}
|