blob: 09cffa3cd19077a723b9a3be18805dee51ead3eb [file] [log] [blame]
/* GNU m4 -- A simple macro processor
Copyright (C) 1989, 1990, 1991, 1992, 1993, 1994, 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 "m4.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 of length LEN as an integer, reporting warnings on behalf
of ME. */
static int
arg_int (const call_info *me, const char *str, size_t len)
{
char *endp;
long value;
/* TODO - also allow parsing `'a' or `"a' which results in the
numeric value of 'a', as in printf(1). */
if (!len)
{
m4_warn (0, me, _("empty string treated as 0"));
return 0;
}
errno = 0;
value = strtol (str, &endp, 10);
if (endp - str != len)
m4_warn (0, me, _("non-numeric argument %s"),
quotearg_style_mem (locale_quoting_style, str, len));
else if (isspace (to_uchar (*str)))
m4_warn (0, me, _("leading whitespace ignored"));
else if (errno == ERANGE || (int) value != value)
m4_warn (0, me, _("numeric overflow detected"));
return value;
}
/* Parse STR of length LEN as a long, reporting warnings on behalf of
ME. */
static long
arg_long (const call_info *me, const char *str, size_t len)
{
char *endp;
long value;
/* TODO - also allow parsing `'a' or `"a' which results in the
numeric value of 'a', as in printf(1). */
if (!len)
{
m4_warn (0, me, _("empty string treated as 0"));
return 0L;
}
errno = 0;
value = strtol (str, &endp, 10);
if (endp - str != len)
m4_warn (0, me, _("non-numeric argument %s"),
quotearg_style_mem (locale_quoting_style, str, len));
else if (isspace (to_uchar (*str)))
m4_warn (0, me, _("leading whitespace ignored"));
else if (errno == ERANGE)
m4_warn (0, me, _("numeric overflow detected"));
return value;
}
/* Check STR of length LEN for embedded NUL, reporting warnings on
behalf of ME. */
static const char *
arg_string (const call_info *me, const char *str, size_t len)
{
if (strlen (str) < len)
m4_warn (0, me, _("argument %s truncated"),
quotearg_style_mem (locale_quoting_style, str, len));
return str;
}
/* Parse STR of length LEN as a double, reporting warnings on behalf
of ME. */
static double
arg_double (const call_info *me, const char *str, size_t len)
{
char *endp;
double value;
if (!len)
{
m4_warn (0, me, _("empty string treated as 0"));
return 0.0;
}
errno = 0;
value = strtod (str, &endp);
if (endp - str != len)
m4_warn (0, me, _("non-numeric argument %s"),
quotearg_style_mem (locale_quoting_style, str, len));
else if (isspace (to_uchar (*str)))
m4_warn (0, me, _("leading whitespace ignored"));
else if (errno == ERANGE)
m4_warn (0, me, _("numeric overflow detected"));
return value;
}
#define ARG_INT(i, argc, argv) \
((argc <= ++i) ? 0 : arg_int (me, ARG (i), ARG_LEN (i)))
#define ARG_LONG(i, argc, argv) \
((argc <= ++i) ? 0L : arg_long (me, ARG (i), ARG_LEN (i)))
#define ARG_STR(i, argc, argv) \
((argc <= ++i) ? "" : arg_string (me, ARG (i), ARG_LEN (i)))
#define ARG_DOUBLE(i, argc, argv) \
((argc <= ++i) ? 0.0 : arg_double (me, ARG (i), ARG_LEN (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. |
`------------------------------------------------------------------*/
void
expand_format (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);/* Macro name. */
const char *f; /* Format control string. */
size_t f_len; /* Length of f. */
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 = 1; /* 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. */
int result = 0;
enum {CHAR, INT, LONG, DOUBLE, STR} datatype;
f = fmt = ARG (1);
f_len = ARG_LEN (1);
assert (!f[f_len]); /* Requiring a terminating NUL makes parsing simpler. */
memset (ok, 0, sizeof ok);
while (f_len--)
{
c = *fmt++;
if (c != '%')
{
obstack_1grow (obs, c);
continue;
}
if (*fmt == '%')
{
obstack_1grow (obs, '%');
fmt++;
f_len--;
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) && (f_len--, 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++;
f_len--;
}
else
while (isdigit (to_uchar (*fmt)))
{
width = 10 * width + *fmt - '0';
fmt++;
f_len--;
}
/* 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;
f_len--;
if (*(++fmt) == '*')
{
prec = ARG_INT (i, argc, argv);
++fmt;
f_len--;
}
else
{
prec = 0;
while (isdigit (to_uchar (*fmt)))
{
prec = 10 * prec + *fmt - '0';
fmt++;
f_len--;
}
}
}
/* Length modifiers. We don't yet recognize ll, j, t, or z. */
if (*fmt == 'l')
{
*p++ = 'l';
lflag = 1;
fmt++;
f_len--;
ok['c'] = ok['s'] = 0;
}
else if (*fmt == 'h')
{
*p++ = 'h';
fmt++;
f_len--;
if (*fmt == 'h')
{
*p++ = 'h';
fmt++;
f_len--;
}
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] || !f_len)
{
m4_warn (0, me, _("unrecognized specifier in %s"),
quotearg_style_mem (locale_quoting_style, f, ARG_LEN (1)));
valid_format = false;
continue;
}
fmt++;
f_len--;
/* 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:
result = obstack_printf (obs, fstart, width,
ARG_INT (i, argc, argv));
break;
case INT:
result = obstack_printf (obs, fstart, width, prec,
ARG_INT (i, argc, argv));
break;
case LONG:
result = obstack_printf (obs, fstart, width, prec,
ARG_LONG (i, argc, argv));
break;
case DOUBLE:
result = obstack_printf (obs, fstart, width, prec,
ARG_DOUBLE (i, argc, argv));
break;
case STR:
result = obstack_printf (obs, fstart, width, prec,
ARG_STR (i, argc, argv));
break;
default:
abort ();
}
/* Since obstack_printf can only fail with EILSEQ or EINVAL, but
we constructed fstart, the result should not be negative. */
assert (0 <= result);
}
if (valid_format)
bad_argc (me, argc, i, i);
}