fix: noconfirm auto-selects first AUR match
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
## Makefile.am - template for generating Makefile via Automake
|
||||
##
|
||||
## Copyright (C) 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/>.
|
||||
##
|
||||
## This file written by Eric Blake <ebb9@byu.net>
|
||||
|
||||
AUTOMAKE_OPTIONS = nostdinc
|
||||
AM_CPPFLAGS = -I$(top_srcdir)/lib -I../lib
|
||||
AM_CFLAGS = $(WARN_CFLAGS) $(WERROR_CFLAGS)
|
||||
AM_LDFLAGS = $(OS2_LDFLAGS)
|
||||
bin_PROGRAMS = m4
|
||||
noinst_HEADERS = m4.h
|
||||
m4_SOURCES = m4.c builtin.c debug.c eval.c format.c freeze.c input.c \
|
||||
macro.c output.c path.c symtab.c
|
||||
LDADD = ../lib/libm4.a $(LIBM4_LIBDEPS) \
|
||||
$(CLOCK_TIME_LIB) $(GETLOCALENAME_L_LIB) $(GETRANDOM_LIB) \
|
||||
$(HARD_LOCALE_LIB) $(LIBPMULTITHREAD) $(LIBPTHREAD) $(LIBTHREAD) \
|
||||
$(LIBC32CONV) $(LIBCSTACK) $(LIBICONV) $(LIBINTL) $(LIBSIGSEGV) \
|
||||
$(LIBUNISTRING) $(MBRTOWC_LIB) $(POSIX_SPAWN_LIB) $(SETLOCALE_LIB) \
|
||||
$(SETLOCALE_NULL_LIB) $(INTL_MACOSX_LIBS)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,436 @@
|
||||
/* GNU m4 -- A simple macro processor
|
||||
|
||||
Copyright (C) 1991-1994, 2004, 2006-2007, 2009-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/>.
|
||||
*/
|
||||
|
||||
#include "m4.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
/* File for debugging output. */
|
||||
FILE *debug = NULL;
|
||||
|
||||
/* Obstack for trace messages. */
|
||||
static struct obstack trace;
|
||||
|
||||
static void debug_set_file (FILE *);
|
||||
|
||||
/*----------------------------------.
|
||||
| Initialise the debugging module. |
|
||||
`----------------------------------*/
|
||||
|
||||
void
|
||||
debug_init (void)
|
||||
{
|
||||
debug_set_file (stderr);
|
||||
obstack_init (&trace);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------.
|
||||
| Function to decode the debugging flags OPTS. Used by main while |
|
||||
| processing option -d, and by the builtin debugmode (). |
|
||||
`-----------------------------------------------------------------*/
|
||||
|
||||
int
|
||||
debug_decode (const char *opts)
|
||||
{
|
||||
int level;
|
||||
|
||||
if (opts == NULL || *opts == '\0')
|
||||
level = DEBUG_TRACE_DEFAULT;
|
||||
else
|
||||
{
|
||||
for (level = 0; *opts; opts++)
|
||||
{
|
||||
switch (*opts)
|
||||
{
|
||||
case 'a':
|
||||
level |= DEBUG_TRACE_ARGS;
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
level |= DEBUG_TRACE_EXPANSION;
|
||||
break;
|
||||
|
||||
case 'q':
|
||||
level |= DEBUG_TRACE_QUOTE;
|
||||
break;
|
||||
|
||||
case 't':
|
||||
level |= DEBUG_TRACE_ALL;
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
level |= DEBUG_TRACE_LINE;
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
level |= DEBUG_TRACE_FILE;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
level |= DEBUG_TRACE_PATH;
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
level |= DEBUG_TRACE_CALL;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
level |= DEBUG_TRACE_INPUT;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
level |= DEBUG_TRACE_CALLID;
|
||||
break;
|
||||
|
||||
case 'V':
|
||||
level |= DEBUG_TRACE_VERBOSE;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* This is to avoid screwing up the trace output due to changes in the
|
||||
debug_level. */
|
||||
|
||||
obstack_free (&trace, obstack_finish (&trace));
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------.
|
||||
| Change the debug output stream to FP. If the underlying file is |
|
||||
| the same as stdout, use stdout instead so that debug messages |
|
||||
| appear in the correct relative position. |
|
||||
`-----------------------------------------------------------------*/
|
||||
|
||||
static void
|
||||
debug_set_file (FILE *fp)
|
||||
{
|
||||
struct stat stdout_stat, debug_stat;
|
||||
|
||||
if (debug != NULL && debug != stderr && debug != stdout
|
||||
&& close_stream (debug) != 0)
|
||||
{
|
||||
M4ERROR ((warning_status, errno, _("error writing to debug stream")));
|
||||
retcode = EXIT_FAILURE;
|
||||
}
|
||||
debug = fp;
|
||||
|
||||
if (debug != NULL && debug != stdout)
|
||||
{
|
||||
if (fstat (STDOUT_FILENO, &stdout_stat) < 0)
|
||||
return;
|
||||
if (fstat (fileno (debug), &debug_stat) < 0)
|
||||
return;
|
||||
|
||||
/* mingw has a bug where fstat on a regular file reports st_ino
|
||||
of 0. On normal system, st_ino should never be 0. */
|
||||
if (stdout_stat.st_ino == debug_stat.st_ino
|
||||
&& stdout_stat.st_dev == debug_stat.st_dev
|
||||
&& stdout_stat.st_ino != 0)
|
||||
{
|
||||
if (debug != stderr && close_stream (debug) != 0)
|
||||
{
|
||||
M4ERROR ((warning_status, errno,
|
||||
_("error writing to debug stream")));
|
||||
retcode = EXIT_FAILURE;
|
||||
}
|
||||
debug = stdout;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------.
|
||||
| Serialize files. Used before executing a system command. |
|
||||
`-----------------------------------------------------------*/
|
||||
|
||||
void
|
||||
debug_flush_files (void)
|
||||
{
|
||||
fflush (stdout);
|
||||
fflush (stderr);
|
||||
if (debug != NULL && debug != stdout && debug != stderr)
|
||||
fflush (debug);
|
||||
/* POSIX requires that if m4 doesn't consume all input, but stdin is
|
||||
opened on a seekable file, that the file pointer be left at the
|
||||
next character on exit (but places no restrictions on the file
|
||||
pointer location on a non-seekable file). It also requires that
|
||||
fflush() followed by fseeko() on an input file set the underlying
|
||||
file pointer, and gnulib guarantees these semantics. However,
|
||||
fflush() on a non-seekable file can lose buffered data, which we
|
||||
might otherwise want to process after syscmd. Hence, we must
|
||||
check whether stdin is seekable. We must also be tolerant of
|
||||
operating with stdin closed, so we don't report any failures in
|
||||
this attempt. The stdio-safer module and friends are essential,
|
||||
so that if stdin was closed, this lseek is not on some other file
|
||||
that we have since opened. */
|
||||
if (lseek (STDIN_FILENO, 0, SEEK_CUR) >= 0 && fflush (stdin) == 0)
|
||||
{
|
||||
fseeko (stdin, 0, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------.
|
||||
| Change the debug output to file NAME. If NAME is NULL, debug |
|
||||
| output is reverted to stderr, and if empty, debug output is |
|
||||
| discarded. Return true iff the output stream was changed. |
|
||||
`--------------------------------------------------------------*/
|
||||
|
||||
bool
|
||||
debug_set_output (const char *name)
|
||||
{
|
||||
FILE *fp;
|
||||
|
||||
if (name == NULL)
|
||||
debug_set_file (stderr);
|
||||
else if (*name == '\0')
|
||||
debug_set_file (NULL);
|
||||
else
|
||||
{
|
||||
fp = fopen (name, "ae");
|
||||
if (fp == NULL)
|
||||
return false;
|
||||
debug_set_file (fp);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------.
|
||||
| Print the header of a one-line debug message, starting by "m4 |
|
||||
| debug". |
|
||||
`--------------------------------------------------------------*/
|
||||
|
||||
void
|
||||
debug_message_prefix (void)
|
||||
{
|
||||
xfprintf (debug, "m4debug:");
|
||||
if (current_line)
|
||||
{
|
||||
if (debug_level & DEBUG_TRACE_FILE)
|
||||
xfprintf (debug, "%s:", current_file);
|
||||
if (debug_level & DEBUG_TRACE_LINE)
|
||||
xfprintf (debug, "%d:", current_line);
|
||||
}
|
||||
putc (' ', debug);
|
||||
}
|
||||
|
||||
/* The rest of this file contains the functions for macro tracing output.
|
||||
All tracing output for a macro call is collected on an obstack TRACE,
|
||||
and printed whenever the line is complete. This prevents tracing
|
||||
output from interfering with other debug messages generated by the
|
||||
various builtins. */
|
||||
|
||||
/*------------------------------------------------------------------.
|
||||
| Tracing output is formatted here, by a simplified |
|
||||
| printf-to-obstack function trace_format (). Understands only %S, |
|
||||
| %s, %d, %l (optional left quote) and %r (optional right quote). |
|
||||
`------------------------------------------------------------------*/
|
||||
|
||||
static void
|
||||
trace_format (const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
char ch;
|
||||
|
||||
int d;
|
||||
const char *s;
|
||||
int slen;
|
||||
int maxlen;
|
||||
|
||||
va_start (args, fmt);
|
||||
|
||||
while (true)
|
||||
{
|
||||
while ((ch = *fmt++) != '\0' && ch != '%')
|
||||
obstack_1grow (&trace, ch);
|
||||
|
||||
if (ch == '\0')
|
||||
break;
|
||||
|
||||
maxlen = 0;
|
||||
switch (*fmt++)
|
||||
{
|
||||
case 'S':
|
||||
maxlen = max_debug_argument_length;
|
||||
FALLTHROUGH;
|
||||
case 's':
|
||||
s = va_arg (args, const char *);
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
s = (debug_level & DEBUG_TRACE_QUOTE) ? lquote.string : "";
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
s = (debug_level & DEBUG_TRACE_QUOTE) ? rquote.string : "";
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
d = va_arg (args, int);
|
||||
s = ntoa (d, 10, NULL);
|
||||
break;
|
||||
|
||||
default:
|
||||
s = "";
|
||||
break;
|
||||
}
|
||||
|
||||
slen = strlen (s);
|
||||
if (maxlen == 0 || maxlen > slen)
|
||||
obstack_grow (&trace, s, slen);
|
||||
else
|
||||
{
|
||||
obstack_grow (&trace, s, maxlen);
|
||||
obstack_grow (&trace, "...", 3);
|
||||
}
|
||||
}
|
||||
|
||||
va_end (args);
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------.
|
||||
| Format the standard header attached to all tracing output lines. |
|
||||
`------------------------------------------------------------------*/
|
||||
|
||||
static void
|
||||
trace_header (int id)
|
||||
{
|
||||
trace_format ("m4trace:");
|
||||
if (current_line)
|
||||
{
|
||||
if (debug_level & DEBUG_TRACE_FILE)
|
||||
trace_format ("%s:", current_file);
|
||||
if (debug_level & DEBUG_TRACE_LINE)
|
||||
trace_format ("%d:", current_line);
|
||||
}
|
||||
trace_format (" -%d- ", expansion_level);
|
||||
if (debug_level & DEBUG_TRACE_CALLID)
|
||||
trace_format ("id %d: ", id);
|
||||
}
|
||||
|
||||
/*----------------------------------------------------.
|
||||
| Print current tracing line, and clear the obstack. |
|
||||
`----------------------------------------------------*/
|
||||
|
||||
static void
|
||||
trace_flush (void)
|
||||
{
|
||||
char *line;
|
||||
|
||||
obstack_1grow (&trace, '\0');
|
||||
line = (char *) obstack_finish (&trace);
|
||||
DEBUG_PRINT1 ("%s\n", line);
|
||||
obstack_free (&trace, line);
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------.
|
||||
| Do pre-argument-collection tracing for macro NAME. Used from |
|
||||
| expand_macro (). |
|
||||
`--------------------------------------------------------------*/
|
||||
|
||||
void
|
||||
trace_prepre (const char *name, int id)
|
||||
{
|
||||
trace_header (id);
|
||||
trace_format ("%s ...", name);
|
||||
trace_flush ();
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------.
|
||||
| Format the parts of a trace line, that can be made before the |
|
||||
| macro is actually expanded. Used from expand_macro (). |
|
||||
`--------------------------------------------------------------*/
|
||||
|
||||
void
|
||||
trace_pre (const char *name, int id, int argc, token_data **argv)
|
||||
{
|
||||
int i;
|
||||
const builtin *bp;
|
||||
|
||||
trace_header (id);
|
||||
trace_format ("%s", name);
|
||||
|
||||
if (argc > 1 && (debug_level & DEBUG_TRACE_ARGS))
|
||||
{
|
||||
trace_format ("(");
|
||||
|
||||
for (i = 1; i < argc; i++)
|
||||
{
|
||||
if (i != 1)
|
||||
trace_format (", ");
|
||||
|
||||
switch (TOKEN_DATA_TYPE (argv[i]))
|
||||
{
|
||||
case TOKEN_TEXT:
|
||||
trace_format ("%l%S%r", TOKEN_DATA_TEXT (argv[i]));
|
||||
break;
|
||||
|
||||
case TOKEN_FUNC:
|
||||
bp = find_builtin_by_addr (TOKEN_DATA_FUNC (argv[i]));
|
||||
if (bp == NULL)
|
||||
{
|
||||
M4ERROR ((warning_status, 0, "\
|
||||
INTERNAL ERROR: builtin not found in builtin table! (trace_pre ())"));
|
||||
abort ();
|
||||
}
|
||||
trace_format ("<%s>", bp->name);
|
||||
break;
|
||||
|
||||
case TOKEN_VOID:
|
||||
default:
|
||||
M4ERROR ((warning_status, 0,
|
||||
"INTERNAL ERROR: bad token data type (trace_pre ())"));
|
||||
abort ();
|
||||
}
|
||||
|
||||
}
|
||||
trace_format (")");
|
||||
}
|
||||
|
||||
if (debug_level & DEBUG_TRACE_CALL)
|
||||
{
|
||||
trace_format (" -> ???");
|
||||
trace_flush ();
|
||||
}
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------.
|
||||
| Format the final part of a trace line and print it all. Used from |
|
||||
| expand_macro (). |
|
||||
`-------------------------------------------------------------------*/
|
||||
|
||||
void
|
||||
trace_post (const char *name, int id, int argc, const char *expanded)
|
||||
{
|
||||
if (debug_level & DEBUG_TRACE_CALL)
|
||||
{
|
||||
trace_header (id);
|
||||
trace_format ("%s%s", name, (argc > 1) ? "(...)" : "");
|
||||
}
|
||||
|
||||
if (expanded && (debug_level & DEBUG_TRACE_EXPANSION))
|
||||
trace_format (" -> %l%S%r", expanded);
|
||||
trace_flush ();
|
||||
}
|
||||
@@ -0,0 +1,615 @@
|
||||
/* GNU m4 -- A simple macro processor
|
||||
|
||||
Copyright (C) 1989-1994, 2006-2007, 2009-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/>.
|
||||
*/
|
||||
|
||||
/* This file contains the functions to evaluate integer expressions for
|
||||
the "eval" macro. It is a little, fairly self-contained module, with
|
||||
its own scanner, and a recursive descent parser. The only entry point
|
||||
is evaluate (). */
|
||||
|
||||
#include "m4.h"
|
||||
|
||||
/* Evaluates token types. */
|
||||
|
||||
#define MIN_PREC 1
|
||||
|
||||
typedef enum eval_token
|
||||
{
|
||||
/* Value / 10 is precedence order, if >= MIN_PREC. */
|
||||
ERROR = 0,
|
||||
BADNUM,
|
||||
BADOP,
|
||||
EOTEXT,
|
||||
LEFTP,
|
||||
RIGHTP,
|
||||
LNOT,
|
||||
NOT,
|
||||
NUMBER,
|
||||
LOR = 10,
|
||||
LAND = 20,
|
||||
OR = 30,
|
||||
XOR = 40,
|
||||
AND = 50,
|
||||
ASSIGN = 60, /* deprecated synonym to EQ */
|
||||
EQ,
|
||||
NOTEQ,
|
||||
GT = 70,
|
||||
GTEQ,
|
||||
LS,
|
||||
LSEQ,
|
||||
LSHIFT = 80,
|
||||
RSHIFT,
|
||||
PLUS = 90, /* precedence for binary op; also serves as a unary op */
|
||||
MINUS, /* precedence for binary op; also serves as a unary op */
|
||||
TIMES = 100,
|
||||
DIVIDE,
|
||||
MODULO,
|
||||
EXPONENT = 110
|
||||
}
|
||||
eval_token;
|
||||
|
||||
/* Error types. */
|
||||
|
||||
typedef enum eval_error
|
||||
{
|
||||
NO_ERROR,
|
||||
DIVIDE_ZERO,
|
||||
MODULO_ZERO,
|
||||
NEGATIVE_EXPONENT,
|
||||
/* All errors prior to SYNTAX_ERROR can be ignored in a dead
|
||||
branch of && and ||. All errors after are just more details
|
||||
about a syntax error. */
|
||||
SYNTAX_ERROR,
|
||||
MISSING_RIGHT,
|
||||
UNKNOWN_INPUT,
|
||||
EXCESS_INPUT,
|
||||
INVALID_NUMBER,
|
||||
INVALID_OPERATOR
|
||||
}
|
||||
eval_error;
|
||||
|
||||
static eval_error primary (int32_t *);
|
||||
static eval_error parse_expr (int32_t *, eval_error, unsigned);
|
||||
|
||||
/*--------------------.
|
||||
| Lexical functions. |
|
||||
`--------------------*/
|
||||
|
||||
/* Pointer to next character of input text. */
|
||||
static const char *eval_text;
|
||||
|
||||
/* Value of eval_text, from before last call of eval_lex (). This is so we
|
||||
can back up, if we have read too much, good for one token lookahead. */
|
||||
static const char *last_text;
|
||||
|
||||
static void
|
||||
eval_init_lex (const char *text)
|
||||
{
|
||||
eval_text = text;
|
||||
last_text = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
eval_undo (void)
|
||||
{
|
||||
eval_text = last_text;
|
||||
}
|
||||
|
||||
/* VAL is numerical value, if any. */
|
||||
|
||||
static eval_token
|
||||
eval_lex (int32_t *val)
|
||||
{
|
||||
while (c_isspace (*eval_text))
|
||||
eval_text++;
|
||||
|
||||
last_text = eval_text;
|
||||
|
||||
if (*eval_text == '\0')
|
||||
return EOTEXT;
|
||||
|
||||
if (c_isdigit (*eval_text))
|
||||
{
|
||||
unsigned int base, digit;
|
||||
/* The documentation says that "overflow silently results in wraparound".
|
||||
Therefore use an unsigned integer type to avoid undefined behaviour
|
||||
when parsing '-2147483648'. */
|
||||
uint32_t value;
|
||||
bool seen_digit = false;
|
||||
|
||||
if (*eval_text == '0')
|
||||
{
|
||||
eval_text++;
|
||||
switch (*eval_text)
|
||||
{
|
||||
case 'x':
|
||||
case 'X':
|
||||
base = 16;
|
||||
eval_text++;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
case 'B':
|
||||
base = 2;
|
||||
eval_text++;
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
case 'R':
|
||||
base = 0;
|
||||
eval_text++;
|
||||
while (c_isdigit (*eval_text) && base <= 36)
|
||||
base = 10 * base + *eval_text++ - '0';
|
||||
if (base == 0 || base > 36 || *eval_text != ':')
|
||||
return BADNUM;
|
||||
eval_text++;
|
||||
break;
|
||||
|
||||
default:
|
||||
base = 8;
|
||||
seen_digit = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
base = 10;
|
||||
|
||||
value = 0;
|
||||
for (; *eval_text; eval_text++, seen_digit = true)
|
||||
{
|
||||
if (c_isdigit (*eval_text))
|
||||
digit = *eval_text - '0';
|
||||
else if (c_islower (*eval_text))
|
||||
digit = *eval_text - 'a' + 10;
|
||||
else if (c_isupper (*eval_text))
|
||||
digit = *eval_text - 'A' + 10;
|
||||
else
|
||||
break;
|
||||
|
||||
if (base == 1)
|
||||
{
|
||||
if (digit == 1)
|
||||
value++;
|
||||
else if (digit == 0 && value == 0)
|
||||
continue;
|
||||
else
|
||||
return BADNUM;
|
||||
}
|
||||
else if (digit >= base)
|
||||
return BADNUM;
|
||||
else
|
||||
value = value * base + digit;
|
||||
}
|
||||
*val = value;
|
||||
if (!seen_digit)
|
||||
return BADNUM;
|
||||
return NUMBER;
|
||||
}
|
||||
|
||||
switch (*eval_text++)
|
||||
{
|
||||
case '+':
|
||||
if (*eval_text == '+' || *eval_text == '=')
|
||||
return BADOP;
|
||||
return PLUS;
|
||||
case '-':
|
||||
if (*eval_text == '-' || *eval_text == '=')
|
||||
return BADOP;
|
||||
return MINUS;
|
||||
case '*':
|
||||
if (*eval_text == '*')
|
||||
{
|
||||
eval_text++;
|
||||
return EXPONENT;
|
||||
}
|
||||
else if (*eval_text == '=')
|
||||
return BADOP;
|
||||
return TIMES;
|
||||
case '/':
|
||||
if (*eval_text == '=')
|
||||
return BADOP;
|
||||
return DIVIDE;
|
||||
case '%':
|
||||
if (*eval_text == '=')
|
||||
return BADOP;
|
||||
return MODULO;
|
||||
case '=':
|
||||
if (*eval_text == '=')
|
||||
{
|
||||
eval_text++;
|
||||
return EQ;
|
||||
}
|
||||
return ASSIGN;
|
||||
case '!':
|
||||
if (*eval_text == '=')
|
||||
{
|
||||
eval_text++;
|
||||
return NOTEQ;
|
||||
}
|
||||
return LNOT;
|
||||
case '>':
|
||||
if (*eval_text == '=')
|
||||
{
|
||||
eval_text++;
|
||||
return GTEQ;
|
||||
}
|
||||
else if (*eval_text == '>')
|
||||
{
|
||||
if (*++eval_text == '=')
|
||||
return BADOP;
|
||||
return RSHIFT;
|
||||
}
|
||||
return GT;
|
||||
case '<':
|
||||
if (*eval_text == '=')
|
||||
{
|
||||
eval_text++;
|
||||
return LSEQ;
|
||||
}
|
||||
else if (*eval_text == '<')
|
||||
{
|
||||
if (*++eval_text == '=')
|
||||
return BADOP;
|
||||
return LSHIFT;
|
||||
}
|
||||
return LS;
|
||||
case '^':
|
||||
if (*eval_text == '=')
|
||||
return BADOP;
|
||||
return XOR;
|
||||
case '~':
|
||||
return NOT;
|
||||
case '&':
|
||||
if (*eval_text == '&')
|
||||
{
|
||||
eval_text++;
|
||||
return LAND;
|
||||
}
|
||||
else if (*eval_text == '=')
|
||||
return BADOP;
|
||||
return AND;
|
||||
case '|':
|
||||
if (*eval_text == '|')
|
||||
{
|
||||
eval_text++;
|
||||
return LOR;
|
||||
}
|
||||
else if (*eval_text == '=')
|
||||
return BADOP;
|
||||
return OR;
|
||||
case '(':
|
||||
return LEFTP;
|
||||
case ')':
|
||||
return RIGHTP;
|
||||
default:
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------.
|
||||
| Operator precedence parser (based on Pratt parser). |
|
||||
`-----------------------------------------------------*/
|
||||
|
||||
/* Parse `(expr)', unary operators, and numbers. */
|
||||
static eval_error
|
||||
primary (int32_t *v1)
|
||||
{
|
||||
eval_error er;
|
||||
int32_t v2;
|
||||
|
||||
switch (eval_lex (v1))
|
||||
{
|
||||
/* Number */
|
||||
case NUMBER:
|
||||
return NO_ERROR;
|
||||
|
||||
/* Parenthesis */
|
||||
case LEFTP:
|
||||
er = primary (v1);
|
||||
er = parse_expr (v1, er, MIN_PREC);
|
||||
if (er >= SYNTAX_ERROR)
|
||||
return er;
|
||||
switch (eval_lex (&v2))
|
||||
{
|
||||
case ERROR:
|
||||
return UNKNOWN_INPUT;
|
||||
case BADNUM:
|
||||
return INVALID_NUMBER;
|
||||
case BADOP:
|
||||
return INVALID_OPERATOR;
|
||||
case RIGHTP:
|
||||
return er;
|
||||
default:
|
||||
return MISSING_RIGHT;
|
||||
}
|
||||
|
||||
/* Unary operators */
|
||||
/* Minimize undefined C behavior on overflow. This code assumes
|
||||
that the implementation-defined overflow when casting
|
||||
unsigned to signed is a silent twos-complement
|
||||
wrap-around. */
|
||||
case PLUS:
|
||||
return primary (v1);
|
||||
case MINUS:
|
||||
er = primary (v1);
|
||||
*v1 = (int32_t) -(uint32_t) *v1;
|
||||
return er;
|
||||
case NOT:
|
||||
er = primary (v1);
|
||||
*v1 = ~*v1;
|
||||
return er;
|
||||
case LNOT:
|
||||
er = primary (v1);
|
||||
*v1 = *v1 == 0 ? 1 : 0;
|
||||
return er;
|
||||
|
||||
/* Anything else */
|
||||
case ERROR:
|
||||
return UNKNOWN_INPUT;
|
||||
case BADNUM:
|
||||
return INVALID_NUMBER;
|
||||
case BADOP:
|
||||
return INVALID_OPERATOR;
|
||||
default:
|
||||
return SYNTAX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse binary operators with at least MIN_PREC precedence. */
|
||||
static eval_error
|
||||
parse_expr (int32_t *v1, eval_error er, unsigned min_prec)
|
||||
{
|
||||
eval_token et;
|
||||
eval_token et2;
|
||||
eval_error er2;
|
||||
int32_t v2;
|
||||
int32_t v3;
|
||||
uint32_t u1;
|
||||
uint32_t u2;
|
||||
uint32_t u3;
|
||||
|
||||
if (er >= SYNTAX_ERROR)
|
||||
return er;
|
||||
et = eval_lex (&v2);
|
||||
while (et / 10 >= min_prec)
|
||||
{
|
||||
if ((er2 = primary (&v2)) >= SYNTAX_ERROR)
|
||||
return er2;
|
||||
et2 = eval_lex (&v3);
|
||||
/* Handle binary operators of higher precedence or right-associativity */
|
||||
while (et2 / 10 > et / 10 || et2 == EXPONENT)
|
||||
{
|
||||
eval_undo ();
|
||||
if ((er2 = parse_expr (&v2, er2, et2 / 10)) >= SYNTAX_ERROR)
|
||||
return er2;
|
||||
et2 = eval_lex (&v3);
|
||||
}
|
||||
/* Reduce the two values by the given binary operator */
|
||||
switch (et)
|
||||
{
|
||||
case EXPONENT:
|
||||
/* Minimize undefined C behavior on overflow. This code assumes
|
||||
that the implementation-defined overflow when casting
|
||||
unsigned to signed is a silent twos-complement
|
||||
wrap-around. */
|
||||
if (v2 < 0)
|
||||
er = NEGATIVE_EXPONENT;
|
||||
else if (*v1 == 0 && v2 == 0)
|
||||
er = DIVIDE_ZERO;
|
||||
else
|
||||
{
|
||||
u1 = *v1;
|
||||
u2 = v2;
|
||||
u3 = 1;
|
||||
while (u2)
|
||||
{
|
||||
if (u2 & 1)
|
||||
u3 *= u1;
|
||||
u1 *= u1;
|
||||
u2 >>= 1;
|
||||
}
|
||||
*v1 = u3;
|
||||
}
|
||||
break;
|
||||
|
||||
case TIMES:
|
||||
*v1 = (int32_t) ((uint32_t) *v1 * (uint32_t) v2);
|
||||
break;
|
||||
case DIVIDE:
|
||||
if (v2 == 0)
|
||||
er = DIVIDE_ZERO;
|
||||
else if (v2 == -1)
|
||||
/* Avoid overflow, and the x86 SIGFPE on INT_MIN / -1. */
|
||||
*v1 = (int32_t) -(uint32_t) *v1;
|
||||
else
|
||||
*v1 /= v2;
|
||||
break;
|
||||
case MODULO:
|
||||
if (v2 == 0)
|
||||
er = MODULO_ZERO;
|
||||
else if (v2 == -1)
|
||||
/* Avoid the x86 SIGFPE on INT_MIN % -1. */
|
||||
*v1 = 0;
|
||||
else
|
||||
*v1 %= v2;
|
||||
break;
|
||||
|
||||
case PLUS:
|
||||
*v1 = (int32_t) ((uint32_t) *v1 + (uint32_t) v2);
|
||||
break;
|
||||
case MINUS:
|
||||
*v1 = (int32_t) ((uint32_t) *v1 - (uint32_t) v2);
|
||||
break;
|
||||
|
||||
case LSHIFT:
|
||||
u1 = *v1;
|
||||
u1 <<= (uint32_t) (v2 & 0x1f);
|
||||
*v1 = u1;
|
||||
break;
|
||||
case RSHIFT:
|
||||
u1 = *v1 < 0 ? ~*v1 : *v1;
|
||||
u1 >>= (uint32_t) (v2 & 0x1f);
|
||||
*v1 = *v1 < 0 ? ~u1 : u1;
|
||||
break;
|
||||
|
||||
case GT:
|
||||
*v1 = *v1 > v2;
|
||||
break;
|
||||
case GTEQ:
|
||||
*v1 = *v1 >= v2;
|
||||
break;
|
||||
case LS:
|
||||
*v1 = *v1 < v2;
|
||||
break;
|
||||
case LSEQ:
|
||||
*v1 = *v1 <= v2;
|
||||
break;
|
||||
|
||||
case ASSIGN:
|
||||
M4ERROR ((warning_status, 0, _("\
|
||||
Warning: recommend ==, not =, for equality operator")));
|
||||
FALLTHROUGH;
|
||||
case EQ:
|
||||
*v1 = *v1 == v2;
|
||||
break;
|
||||
case NOTEQ:
|
||||
*v1 = *v1 != v2;
|
||||
break;
|
||||
|
||||
case AND:
|
||||
*v1 &= v2;
|
||||
break;
|
||||
|
||||
case XOR:
|
||||
*v1 ^= v2;
|
||||
break;
|
||||
|
||||
case OR:
|
||||
*v1 |= v2;
|
||||
break;
|
||||
|
||||
/* Implement short-circuiting of valid syntax. */
|
||||
case LAND:
|
||||
if (!*v1)
|
||||
er2 = NO_ERROR;
|
||||
*v1 = *v1 && v2;
|
||||
break;
|
||||
|
||||
case LOR:
|
||||
if (*v1)
|
||||
er2 = NO_ERROR;
|
||||
*v1 = *v1 || v2;
|
||||
break;
|
||||
|
||||
default:
|
||||
M4ERROR ((warning_status, 0,
|
||||
"INTERNAL ERROR: unexpected operator in evaluate ()"));
|
||||
abort ();
|
||||
}
|
||||
if (er == NO_ERROR)
|
||||
er = er2;
|
||||
et = et2;
|
||||
}
|
||||
|
||||
eval_undo ();
|
||||
return er;
|
||||
}
|
||||
|
||||
/*---------------------------------------.
|
||||
| Main entry point, called from "eval". |
|
||||
`---------------------------------------*/
|
||||
|
||||
bool
|
||||
evaluate (const char *expr, int32_t *val)
|
||||
{
|
||||
eval_error err;
|
||||
|
||||
eval_init_lex (expr);
|
||||
err = primary (val);
|
||||
err = parse_expr (val, err, MIN_PREC);
|
||||
|
||||
if (err == NO_ERROR && *eval_text != '\0')
|
||||
{
|
||||
switch (eval_lex (val))
|
||||
{
|
||||
case BADNUM:
|
||||
err = INVALID_NUMBER;
|
||||
break;
|
||||
case BADOP:
|
||||
err = INVALID_OPERATOR;
|
||||
break;
|
||||
default:
|
||||
err = EXCESS_INPUT;
|
||||
}
|
||||
}
|
||||
|
||||
switch (err)
|
||||
{
|
||||
case NO_ERROR:
|
||||
break;
|
||||
|
||||
case MISSING_RIGHT:
|
||||
M4ERROR ((warning_status, 0,
|
||||
_("bad expression in eval (missing right parenthesis): %s"),
|
||||
expr));
|
||||
break;
|
||||
|
||||
case SYNTAX_ERROR:
|
||||
M4ERROR ((warning_status, 0, _("bad expression in eval: %s"), expr));
|
||||
break;
|
||||
|
||||
case UNKNOWN_INPUT:
|
||||
M4ERROR ((warning_status, 0,
|
||||
_("bad expression in eval (bad input): %s"), expr));
|
||||
break;
|
||||
|
||||
case EXCESS_INPUT:
|
||||
M4ERROR ((warning_status, 0,
|
||||
_("bad expression in eval (excess input): %s"), expr));
|
||||
break;
|
||||
|
||||
case INVALID_NUMBER:
|
||||
M4ERROR ((warning_status, 0, _("invalid number in eval: %s"), expr));
|
||||
break;
|
||||
|
||||
case INVALID_OPERATOR:
|
||||
M4ERROR ((warning_status, 0, _("invalid operator in eval: %s"), expr));
|
||||
retcode = EXIT_FAILURE;
|
||||
break;
|
||||
|
||||
case DIVIDE_ZERO:
|
||||
M4ERROR ((warning_status, 0, _("divide by zero in eval: %s"), expr));
|
||||
break;
|
||||
|
||||
case MODULO_ZERO:
|
||||
M4ERROR ((warning_status, 0, _("modulo by zero in eval: %s"), expr));
|
||||
break;
|
||||
|
||||
case NEGATIVE_EXPONENT:
|
||||
M4ERROR ((warning_status, 0, _("negative exponent in eval: %s"), expr));
|
||||
break;
|
||||
|
||||
default:
|
||||
M4ERROR ((warning_status, 0,
|
||||
"INTERNAL ERROR: bad error code in evaluate ()"));
|
||||
abort ();
|
||||
}
|
||||
|
||||
return err != NO_ERROR;
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
/* 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/>.
|
||||
*/
|
||||
|
||||
/* This module handles frozen files. */
|
||||
|
||||
#include "m4.h"
|
||||
|
||||
/*-------------------------------------------------------------------.
|
||||
| Destructively reverse a symbol list and return the reversed list. |
|
||||
`-------------------------------------------------------------------*/
|
||||
|
||||
static symbol *
|
||||
reverse_symbol_list (symbol *sym)
|
||||
{
|
||||
symbol *result;
|
||||
symbol *next;
|
||||
|
||||
result = NULL;
|
||||
while (sym)
|
||||
{
|
||||
next = SYMBOL_STACK (sym);
|
||||
SYMBOL_STACK (sym) = result;
|
||||
result = sym;
|
||||
sym = next;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
freeze_symbol (symbol *sym, void *arg)
|
||||
{
|
||||
symbol *s = sym;
|
||||
FILE *file = arg;
|
||||
const builtin *bp;
|
||||
|
||||
/* Process all entries in one stack, from the last to the first.
|
||||
This order ensures that, at reload time, pushdef's will be
|
||||
executed with the oldest definitions first. */
|
||||
|
||||
s = reverse_symbol_list (s);
|
||||
for (sym = s; sym; sym = SYMBOL_STACK (sym))
|
||||
{
|
||||
switch (SYMBOL_TYPE (sym))
|
||||
{
|
||||
case TOKEN_TEXT:
|
||||
xfprintf (file, "T%d,%d\n",
|
||||
(int) strlen (SYMBOL_NAME (sym)),
|
||||
(int) strlen (SYMBOL_TEXT (sym)));
|
||||
fputs (SYMBOL_NAME (sym), file);
|
||||
fputs (SYMBOL_TEXT (sym), file);
|
||||
fputc ('\n', file);
|
||||
break;
|
||||
|
||||
case TOKEN_FUNC:
|
||||
bp = find_builtin_by_addr (SYMBOL_FUNC (sym));
|
||||
if (bp == NULL)
|
||||
{
|
||||
M4ERROR ((warning_status, 0, "\
|
||||
INTERNAL ERROR: builtin not found in builtin table!"));
|
||||
abort ();
|
||||
}
|
||||
xfprintf (file, "F%d,%d\n",
|
||||
(int) strlen (SYMBOL_NAME (sym)),
|
||||
(int) strlen (bp->name));
|
||||
fputs (SYMBOL_NAME (sym), file);
|
||||
fputs (bp->name, file);
|
||||
fputc ('\n', file);
|
||||
break;
|
||||
|
||||
case TOKEN_VOID:
|
||||
/* Ignore placeholder tokens that exist due to traceon. */
|
||||
break;
|
||||
|
||||
default:
|
||||
M4ERROR ((warning_status, 0, "\
|
||||
INTERNAL ERROR: bad token data type in freeze_symbol ()"));
|
||||
abort ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reverse the stack once more, putting it back as it was. */
|
||||
reverse_symbol_list (s);
|
||||
}
|
||||
|
||||
/*------------------------------------------------.
|
||||
| Produce a frozen state to the given file NAME. |
|
||||
`------------------------------------------------*/
|
||||
|
||||
void
|
||||
produce_frozen_state (const char *name)
|
||||
{
|
||||
FILE *file;
|
||||
|
||||
file = fopen (name, O_BINARY ? "wbe" : "we");
|
||||
if (!file)
|
||||
m4_failure (errno, _("cannot open `%s'"), name);
|
||||
|
||||
/* Write a recognizable header. */
|
||||
|
||||
xfprintf (file, "# This is a frozen state file generated by %s\n",
|
||||
PACKAGE_STRING);
|
||||
xfprintf (file, "V1\n");
|
||||
|
||||
/* Dump quote delimiters. */
|
||||
|
||||
if (strcmp (lquote.string, DEF_LQUOTE)
|
||||
|| strcmp (rquote.string, DEF_RQUOTE))
|
||||
{
|
||||
xfprintf (file, "Q%d,%d\n", (int) lquote.length, (int) rquote.length);
|
||||
fputs (lquote.string, file);
|
||||
fputs (rquote.string, file);
|
||||
fputc ('\n', file);
|
||||
}
|
||||
|
||||
/* Dump comment delimiters. */
|
||||
|
||||
if (strcmp (bcomm.string, DEF_BCOMM) || strcmp (ecomm.string, DEF_ECOMM))
|
||||
{
|
||||
xfprintf (file, "C%d,%d\n", (int) bcomm.length, (int) ecomm.length);
|
||||
fputs (bcomm.string, file);
|
||||
fputs (ecomm.string, file);
|
||||
fputc ('\n', file);
|
||||
}
|
||||
|
||||
/* Dump all symbols. */
|
||||
|
||||
hack_all_symbols (freeze_symbol, file);
|
||||
|
||||
/* Let diversions be issued from output.c module, its cleaner to have this
|
||||
piece of code there. */
|
||||
|
||||
freeze_diversions (file);
|
||||
|
||||
/* All done. */
|
||||
|
||||
fputs ("# End of frozen state file\n", file);
|
||||
if (close_stream (file) != 0)
|
||||
m4_failure (errno, _("unable to create frozen state"));
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------.
|
||||
| Issue a message saying that some character is an EXPECTED character. |
|
||||
`----------------------------------------------------------------------*/
|
||||
|
||||
static void
|
||||
issue_expect_message (int expected)
|
||||
{
|
||||
if (expected == '\n')
|
||||
m4_failure (0, _("expecting line feed in frozen file"));
|
||||
else
|
||||
m4_failure (0, _("expecting character `%c' in frozen file"), expected);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------.
|
||||
| Reload a frozen state from the given file NAME. |
|
||||
`-------------------------------------------------*/
|
||||
|
||||
/* We are seeking speed, here. */
|
||||
|
||||
void
|
||||
reload_frozen_state (const char *name)
|
||||
{
|
||||
FILE *file;
|
||||
int character;
|
||||
int operation;
|
||||
char *string[2];
|
||||
int allocated[2];
|
||||
int number[2];
|
||||
const builtin *bp;
|
||||
bool advance_line = true;
|
||||
|
||||
#define GET_CHARACTER \
|
||||
do \
|
||||
{ \
|
||||
if (advance_line) \
|
||||
{ \
|
||||
current_line++; \
|
||||
advance_line = false; \
|
||||
} \
|
||||
(character = getc (file)); \
|
||||
if (character == '\n') \
|
||||
advance_line = true; \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
#define GET_NUMBER(Number, AllowNeg) \
|
||||
do \
|
||||
{ \
|
||||
unsigned int n = 0; \
|
||||
while (c_isdigit (character) && n <= INT_MAX / 10U) \
|
||||
{ \
|
||||
n = 10 * n + character - '0'; \
|
||||
GET_CHARACTER; \
|
||||
} \
|
||||
if (((AllowNeg) ? INT_MIN : INT_MAX) + 0U < n \
|
||||
|| c_isdigit (character)) \
|
||||
m4_failure (0, _("integer overflow in frozen file")); \
|
||||
(Number) = n; \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
#define VALIDATE(Expected) \
|
||||
do \
|
||||
{ \
|
||||
if (character != (Expected)) \
|
||||
issue_expect_message (Expected); \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
/* Skip comments (`#' at beginning of line) and blank lines, setting
|
||||
character to the next directive or to EOF. */
|
||||
|
||||
#define GET_DIRECTIVE \
|
||||
do \
|
||||
{ \
|
||||
GET_CHARACTER; \
|
||||
if (character == '#') \
|
||||
{ \
|
||||
while (character != EOF && character != '\n') \
|
||||
GET_CHARACTER; \
|
||||
VALIDATE ('\n'); \
|
||||
} \
|
||||
} \
|
||||
while (character == '\n')
|
||||
|
||||
#define GET_STRING(i) \
|
||||
do \
|
||||
{ \
|
||||
void *tmp; \
|
||||
char *p; \
|
||||
if (number[(i)] + 1 > allocated[(i)]) \
|
||||
{ \
|
||||
free (string[(i)]); \
|
||||
allocated[(i)] = number[(i)] + 1; \
|
||||
string[(i)] = xcharalloc ((size_t) allocated[(i)]); \
|
||||
} \
|
||||
if (number[(i)] > 0 \
|
||||
&& !fread (string[(i)], (size_t) number[(i)], 1, file)) \
|
||||
m4_failure (0, _("premature end of frozen file")); \
|
||||
string[(i)][number[(i)]] = '\0'; \
|
||||
p = string[(i)]; \
|
||||
while ((tmp = memchr(p, '\n', number[(i)] - (p - string[(i)])))) \
|
||||
{ \
|
||||
current_line++; \
|
||||
p = (char *) tmp + 1; \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
file = m4_path_search (name, !!O_BINARY, NULL);
|
||||
if (file == NULL)
|
||||
m4_failure (errno, _("cannot open %s"), name);
|
||||
current_file = name;
|
||||
|
||||
allocated[0] = 100;
|
||||
string[0] = xcharalloc ((size_t) allocated[0]);
|
||||
allocated[1] = 100;
|
||||
string[1] = xcharalloc ((size_t) allocated[1]);
|
||||
|
||||
/* Validate format version. Only `1' is acceptable for now. */
|
||||
GET_DIRECTIVE;
|
||||
VALIDATE ('V');
|
||||
GET_CHARACTER;
|
||||
GET_NUMBER (number[0], false);
|
||||
if (number[0] > 1)
|
||||
M4ERROR ((EXIT_MISMATCH, 0,
|
||||
_("frozen file version %d greater than max supported of 1"),
|
||||
number[0]));
|
||||
else if (number[0] < 1)
|
||||
m4_failure (0, _("ill-formed frozen file, version directive expected"));
|
||||
VALIDATE ('\n');
|
||||
|
||||
GET_DIRECTIVE;
|
||||
while (character != EOF)
|
||||
{
|
||||
switch (character)
|
||||
{
|
||||
default:
|
||||
m4_failure (0, _("ill-formed frozen file"));
|
||||
|
||||
case 'C':
|
||||
case 'D':
|
||||
case 'F':
|
||||
case 'T':
|
||||
case 'Q':
|
||||
operation = character;
|
||||
GET_CHARACTER;
|
||||
|
||||
/* Get string lengths. Accept a negative diversion number. */
|
||||
|
||||
if (operation == 'D' && character == '-')
|
||||
{
|
||||
GET_CHARACTER;
|
||||
GET_NUMBER (number[0], true);
|
||||
number[0] = -number[0];
|
||||
}
|
||||
else
|
||||
GET_NUMBER (number[0], false);
|
||||
VALIDATE (',');
|
||||
GET_CHARACTER;
|
||||
GET_NUMBER (number[1], false);
|
||||
VALIDATE ('\n');
|
||||
|
||||
if (operation != 'D')
|
||||
GET_STRING (0);
|
||||
GET_STRING (1);
|
||||
GET_CHARACTER;
|
||||
VALIDATE ('\n');
|
||||
|
||||
/* Act according to operation letter. */
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case 'C':
|
||||
|
||||
/* Change comment strings. */
|
||||
|
||||
set_comment (string[0], string[1]);
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
|
||||
/* Select a diversion and add a string to it. */
|
||||
|
||||
make_diversion (number[0]);
|
||||
if (number[1] > 0)
|
||||
output_text (string[1], number[1]);
|
||||
break;
|
||||
|
||||
case 'F':
|
||||
|
||||
/* Enter a macro having a builtin function as a definition. */
|
||||
|
||||
bp = find_builtin_by_name (string[1]);
|
||||
define_builtin (string[0], bp, SYMBOL_PUSHDEF);
|
||||
break;
|
||||
|
||||
case 'T':
|
||||
|
||||
/* Enter a macro having an expansion text as a definition. */
|
||||
|
||||
define_user_macro (string[0], strlen (string[0]), string[1],
|
||||
strlen (string[1]), SYMBOL_PUSHDEF);
|
||||
break;
|
||||
|
||||
case 'Q':
|
||||
|
||||
/* Change quote strings. */
|
||||
|
||||
set_quotes (string[0], string[1]);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
/* Cannot happen. */
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
GET_DIRECTIVE;
|
||||
}
|
||||
|
||||
free (string[0]);
|
||||
free (string[1]);
|
||||
if (close_stream (file) != 0)
|
||||
m4_failure (errno, _("unable to read frozen state"));
|
||||
current_file = NULL;
|
||||
current_line = 0;
|
||||
|
||||
#undef GET_CHARACTER
|
||||
#undef GET_DIRECTIVE
|
||||
#undef GET_NUMBER
|
||||
#undef VALIDATE
|
||||
#undef GET_STRING
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,743 @@
|
||||
/* GNU m4 -- A simple macro processor
|
||||
|
||||
Copyright (C) 1989-1994, 2004-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/>.
|
||||
*/
|
||||
|
||||
#include "m4.h"
|
||||
|
||||
#include <getopt.h>
|
||||
#include <limits.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "c-stack.h"
|
||||
#include "configmake.h"
|
||||
#include "ignore-value.h"
|
||||
#include "progname.h"
|
||||
#include "propername.h"
|
||||
#include "version-etc.h"
|
||||
|
||||
#ifdef DEBUG_STKOVF
|
||||
# include <assert.h>
|
||||
#endif
|
||||
|
||||
/* TRANSLATORS: This is a non-ASCII name: The first name is (with
|
||||
Unicode escapes) "Ren\u00e9" or (with HTML entities) "René". */
|
||||
#define AUTHORS proper_name_utf8 ("Rene' Seindal", "Ren\xC3\xA9 Seindal")
|
||||
|
||||
static _Noreturn void usage (int);
|
||||
|
||||
/* Enable sync output for /lib/cpp (-s). */
|
||||
int sync_output = 0;
|
||||
|
||||
/* Debug (-d[flags]). */
|
||||
int debug_level = 0;
|
||||
|
||||
/* Hash table size (should be a prime) (-Hsize). */
|
||||
size_t hash_table_size = HASHMAX;
|
||||
|
||||
/* Disable GNU extensions (-G). */
|
||||
int no_gnu_extensions = 0;
|
||||
|
||||
/* Prefix all builtin functions by `m4_'. */
|
||||
int prefix_all_builtins = 0;
|
||||
|
||||
/* Max length of arguments in trace output (-lsize). */
|
||||
int max_debug_argument_length = 0;
|
||||
|
||||
/* Suppress warnings about missing arguments. */
|
||||
int suppress_warnings = 0;
|
||||
|
||||
/* If true, then warnings affect exit status. */
|
||||
static bool fatal_warnings = false;
|
||||
|
||||
/* If not zero, then value of exit status for warning diagnostics. */
|
||||
int warning_status = 0;
|
||||
|
||||
/* Artificial limit for expansion_level in macro.c. */
|
||||
int nesting_limit = 1024;
|
||||
|
||||
#ifdef ENABLE_CHANGEWORD
|
||||
/* User provided regexp for describing m4 words. */
|
||||
const char *user_word_regexp = "";
|
||||
#endif
|
||||
|
||||
/* Global catchall for any errors that should affect final error status, but
|
||||
where we try to continue execution in the meantime. */
|
||||
int retcode;
|
||||
|
||||
struct macro_definition
|
||||
{
|
||||
struct macro_definition *next;
|
||||
int code; /* D, U, s, t, '\1', or DEBUGFILE_OPTION. */
|
||||
const char *arg;
|
||||
};
|
||||
typedef struct macro_definition macro_definition;
|
||||
|
||||
/* Error handling functions. */
|
||||
|
||||
/*-----------------------.
|
||||
| Wrapper around error. |
|
||||
`-----------------------*/
|
||||
|
||||
void
|
||||
m4_error (int status, int errnum, const char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start (args, format);
|
||||
verror_at_line (status, errnum, current_line ? current_file : NULL,
|
||||
current_line, format, args);
|
||||
if (fatal_warnings && !retcode)
|
||||
retcode = EXIT_FAILURE;
|
||||
va_end (args);
|
||||
}
|
||||
|
||||
void
|
||||
m4_failure (int errnum, const char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start (args, format);
|
||||
verror_at_line (EXIT_FAILURE, errnum, current_line ? current_file : NULL,
|
||||
current_line, format, args);
|
||||
assume (false);
|
||||
}
|
||||
|
||||
/*-------------------------------.
|
||||
| Wrapper around error_at_line. |
|
||||
`-------------------------------*/
|
||||
|
||||
void
|
||||
m4_error_at_line (int status, int errnum, const char *file, int line,
|
||||
const char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start (args, format);
|
||||
verror_at_line (status, errnum, line ? file : NULL, line, format, args);
|
||||
if (fatal_warnings && !retcode)
|
||||
retcode = EXIT_FAILURE;
|
||||
va_end (args);
|
||||
}
|
||||
|
||||
void
|
||||
m4_failure_at_line (int errnum, const char *file, int line,
|
||||
const char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start (args, format);
|
||||
verror_at_line (EXIT_FAILURE, errnum, line ? file : NULL,
|
||||
line, format, args);
|
||||
assume (false);
|
||||
}
|
||||
|
||||
#ifndef SIGBUS
|
||||
# define SIGBUS SIGILL
|
||||
#endif
|
||||
|
||||
#ifndef NSIG
|
||||
# ifndef MAX
|
||||
# define MAX(a,b) ((a) < (b) ? (b) : (a))
|
||||
# endif
|
||||
# define NSIG (MAX (SIGABRT, MAX (SIGILL, MAX (SIGFPE, \
|
||||
MAX (SIGSEGV, SIGBUS)))) + 1)
|
||||
#endif
|
||||
|
||||
/* Pre-translated messages for program errors. Do not translate in
|
||||
the signal handler, since gettext and strsignal are not
|
||||
async-signal-safe. */
|
||||
static const char *volatile program_error_message;
|
||||
static const char *volatile signal_message[NSIG];
|
||||
|
||||
/* Print a nicer message about any programmer errors, then exit. This
|
||||
must be aysnc-signal safe, since it is executed as a signal
|
||||
handler. If SIGNO is zero, this represents a stack overflow; in
|
||||
that case, we return to allow c_stack_action to handle things. */
|
||||
static void
|
||||
fault_handler (int signo)
|
||||
{
|
||||
if (signo)
|
||||
{
|
||||
/* POSIX states that reading static memory is, in general, not
|
||||
async-safe. However, the static variables that we read are
|
||||
never modified once this handler is installed, so this
|
||||
particular usage is safe. And it seems an oversight that
|
||||
POSIX claims strlen is not async-safe. Ignore write
|
||||
failures, since we will exit with non-zero status anyway. */
|
||||
#define WRITE(f, b, l) ignore_value (write (f, b, l))
|
||||
WRITE (STDERR_FILENO, program_name, strlen (program_name));
|
||||
WRITE (STDERR_FILENO, ": ", 2);
|
||||
WRITE (STDERR_FILENO, program_error_message,
|
||||
strlen (program_error_message));
|
||||
if (signal_message[signo])
|
||||
{
|
||||
WRITE (STDERR_FILENO, ": ", 2);
|
||||
WRITE (STDERR_FILENO, signal_message[signo],
|
||||
strlen (signal_message[signo]));
|
||||
}
|
||||
WRITE (STDERR_FILENO, "\n", 1);
|
||||
#undef WRITE
|
||||
_exit (EXIT_INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*---------------------------------------------.
|
||||
| Print a usage message and exit with STATUS. |
|
||||
`---------------------------------------------*/
|
||||
|
||||
static void
|
||||
usage (int status)
|
||||
{
|
||||
if (status != EXIT_SUCCESS)
|
||||
{
|
||||
xfprintf (stderr, _("Try `%s --help' for more information."),
|
||||
program_name);
|
||||
fputs ("\n", stderr);
|
||||
}
|
||||
else
|
||||
{
|
||||
xprintf (_("Usage: %s [OPTION]... [FILE]...\n"), program_name);
|
||||
fputs (_("\
|
||||
Process macros in FILEs. If no FILE or if FILE is `-', standard input\n\
|
||||
is read.\n\
|
||||
"), stdout);
|
||||
puts ("");
|
||||
fputs (_("\
|
||||
Mandatory or optional arguments to long options are mandatory or optional\n\
|
||||
for short options too.\n\
|
||||
"), stdout);
|
||||
puts ("");
|
||||
fputs (_("\
|
||||
Operation modes:\n\
|
||||
--help display this help and exit\n\
|
||||
--version output version information and exit\n\
|
||||
"), stdout);
|
||||
fputs (_("\
|
||||
-E, --fatal-warnings once: warnings become errors, twice: stop\n\
|
||||
execution at first error\n\
|
||||
-i, --interactive unbuffer output, ignore interrupts\n\
|
||||
-P, --prefix-builtins force a `m4_' prefix to all builtins\n\
|
||||
-Q, --quiet, --silent suppress some warnings for builtins\n\
|
||||
"), stdout);
|
||||
xprintf (_("\
|
||||
--warn-macro-sequence[=REGEXP]\n\
|
||||
warn if macro definition matches REGEXP,\n\
|
||||
default %s\n\
|
||||
"), DEFAULT_MACRO_SEQUENCE);
|
||||
#ifdef ENABLE_CHANGEWORD
|
||||
fputs (_("\
|
||||
-W, --word-regexp=REGEXP use REGEXP for macro name syntax\n\
|
||||
"), stdout);
|
||||
#endif
|
||||
puts ("");
|
||||
fputs (_("\
|
||||
Preprocessor features:\n\
|
||||
-D, --define=NAME[=VALUE] define NAME as having VALUE, or empty\n\
|
||||
-I, --include=DIRECTORY append DIRECTORY to include path\n\
|
||||
-s, --synclines generate `#line NUM \"FILE\"' lines\n\
|
||||
-U, --undefine=NAME undefine NAME\n\
|
||||
"), stdout);
|
||||
puts ("");
|
||||
xprintf (_("\
|
||||
Limits control:\n\
|
||||
-g, --gnu override -G to re-enable GNU extensions\n\
|
||||
-G, --traditional suppress all GNU extensions\n\
|
||||
-H, --hashsize=PRIME set symbol lookup hash table size [%d]\n\
|
||||
-L, --nesting-limit=NUMBER change nesting limit, 0 for unlimited [%d]\n\
|
||||
"), HASHMAX, nesting_limit);
|
||||
puts ("");
|
||||
fputs (_("\
|
||||
Frozen state files:\n\
|
||||
-F, --freeze-state=FILE produce a frozen state on FILE at end\n\
|
||||
-R, --reload-state=FILE reload a frozen state from FILE at start\n\
|
||||
"), stdout);
|
||||
puts ("");
|
||||
fputs (_("\
|
||||
Debugging:\n\
|
||||
-d, --debug[=FLAGS] set debug level (no FLAGS implies `aeq')\n\
|
||||
--debugfile[=FILE] redirect debug and trace output to FILE\n\
|
||||
(default stderr, discard if empty string)\n\
|
||||
-l, --arglength=NUM restrict macro tracing size\n\
|
||||
-t, --trace=NAME trace NAME when it is defined\n\
|
||||
"), stdout);
|
||||
puts ("");
|
||||
fputs (_("\
|
||||
FLAGS is any of:\n\
|
||||
a show actual arguments\n\
|
||||
c show before collect, after collect and after call\n\
|
||||
e show expansion\n\
|
||||
f say current input file name\n\
|
||||
i show changes in input files\n\
|
||||
"), stdout);
|
||||
fputs (_("\
|
||||
l say current input line number\n\
|
||||
p show results of path searches\n\
|
||||
q quote values as necessary, with a or e flag\n\
|
||||
t trace for all macro calls, not only traceon'ed\n\
|
||||
x add a unique macro call id, useful with c flag\n\
|
||||
V shorthand for all of the above flags\n\
|
||||
"), stdout);
|
||||
puts ("");
|
||||
fputs (_("\
|
||||
If defined, the environment variable `M4PATH' is a colon-separated list\n\
|
||||
of directories included after any specified by `-I'.\n\
|
||||
"), stdout);
|
||||
puts ("");
|
||||
fputs (_("\
|
||||
Exit status is 0 for success, 1 for failure, 63 for frozen file version\n\
|
||||
mismatch, or whatever value was passed to the m4exit macro.\n\
|
||||
"), stdout);
|
||||
emit_bug_reporting_address ();
|
||||
}
|
||||
exit (status);
|
||||
}
|
||||
|
||||
/*--------------------------------------.
|
||||
| Decode options and launch execution. |
|
||||
`--------------------------------------*/
|
||||
|
||||
/* For long options that have no equivalent short option, use a
|
||||
non-character as a pseudo short option, starting with CHAR_MAX + 1. */
|
||||
enum
|
||||
{
|
||||
DEBUGFILE_OPTION = CHAR_MAX + 1, /* no short opt */
|
||||
DIVERSIONS_OPTION, /* not quite -N, because of message */
|
||||
WARN_MACRO_SEQUENCE_OPTION, /* no short opt */
|
||||
|
||||
HELP_OPTION, /* no short opt */
|
||||
VERSION_OPTION /* no short opt */
|
||||
};
|
||||
|
||||
static const struct option long_options[] = {
|
||||
{"arglength", required_argument, NULL, 'l'},
|
||||
{"debug", optional_argument, NULL, 'd'},
|
||||
{"define", required_argument, NULL, 'D'},
|
||||
|
||||
/* FIXME: deprecate in 2.0 */
|
||||
{"error-output", required_argument, NULL, 'o'},
|
||||
|
||||
{"fatal-warnings", no_argument, NULL, 'E'},
|
||||
{"freeze-state", required_argument, NULL, 'F'},
|
||||
{"gnu", no_argument, NULL, 'g'},
|
||||
{"hashsize", required_argument, NULL, 'H'},
|
||||
{"include", required_argument, NULL, 'I'},
|
||||
{"interactive", no_argument, NULL, 'i'},
|
||||
{"nesting-limit", required_argument, NULL, 'L'},
|
||||
{"prefix-builtins", no_argument, NULL, 'P'},
|
||||
{"quiet", no_argument, NULL, 'Q'},
|
||||
{"reload-state", required_argument, NULL, 'R'},
|
||||
{"silent", no_argument, NULL, 'Q'},
|
||||
{"synclines", no_argument, NULL, 's'},
|
||||
{"trace", required_argument, NULL, 't'},
|
||||
{"traditional", no_argument, NULL, 'G'},
|
||||
{"undefine", required_argument, NULL, 'U'},
|
||||
#ifdef ENABLE_CHANGEWORD
|
||||
{"word-regexp", required_argument, NULL, 'W'},
|
||||
#endif
|
||||
|
||||
{"debugfile", optional_argument, NULL, DEBUGFILE_OPTION},
|
||||
{"diversions", required_argument, NULL, DIVERSIONS_OPTION},
|
||||
{"warn-macro-sequence", optional_argument, NULL,
|
||||
WARN_MACRO_SEQUENCE_OPTION},
|
||||
|
||||
{"help", no_argument, NULL, HELP_OPTION},
|
||||
{"version", no_argument, NULL, VERSION_OPTION},
|
||||
|
||||
{NULL, 0, NULL, 0},
|
||||
};
|
||||
|
||||
/* Process a command line file NAME, and return true only if it was
|
||||
stdin. */
|
||||
static void
|
||||
process_file (const char *name)
|
||||
{
|
||||
if (STREQ (name, "-"))
|
||||
{
|
||||
/* If stdin is a terminal, we want to allow 'm4 - file -'
|
||||
to read input from stdin twice, like GNU cat. Besides,
|
||||
there is no point closing stdin before wrapped text, to
|
||||
minimize bugs in syscmd called from wrapped text. */
|
||||
push_file (stdin, "stdin", false);
|
||||
}
|
||||
else
|
||||
{
|
||||
char *full_name;
|
||||
FILE *fp = m4_path_search (name, false, &full_name);
|
||||
if (fp == NULL)
|
||||
{
|
||||
error (0, errno, _("cannot open `%s'"), name);
|
||||
/* Set the status to EXIT_FAILURE, even though we
|
||||
continue to process files after a missing file. */
|
||||
retcode = EXIT_FAILURE;
|
||||
return;
|
||||
}
|
||||
push_file (fp, full_name, true);
|
||||
free (full_name);
|
||||
}
|
||||
expand_input ();
|
||||
}
|
||||
|
||||
/* POSIX requires only -D, -U, and -s; and says that the first two
|
||||
must be recognized when interspersed with file names. Traditional
|
||||
behavior also handles -s between files. Starting OPTSTRING with
|
||||
'-' forces getopt_long to hand back file names as arguments to opt
|
||||
'\1', rather than reordering the command line. */
|
||||
#ifdef ENABLE_CHANGEWORD
|
||||
# define OPTSTRING "-B:D:EF:GH:I:L:N:PQR:S:T:U:W:d::egil:o:st:"
|
||||
#else
|
||||
# define OPTSTRING "-B:D:EF:GH:I:L:N:PQR:S:T:U:d::egil:o:st:"
|
||||
#endif
|
||||
|
||||
int
|
||||
main (int argc, char *const *argv)
|
||||
{
|
||||
struct sigaction act;
|
||||
macro_definition *head; /* head of deferred argument list */
|
||||
macro_definition *tail;
|
||||
macro_definition *defn;
|
||||
int optchar; /* option character */
|
||||
|
||||
macro_definition *defines;
|
||||
bool interactive = false;
|
||||
bool seen_file = false;
|
||||
const char *debugfile = NULL;
|
||||
const char *frozen_file_to_read = NULL;
|
||||
const char *frozen_file_to_write = NULL;
|
||||
const char *macro_sequence = "";
|
||||
|
||||
set_program_name (argv[0]);
|
||||
retcode = EXIT_SUCCESS;
|
||||
setlocale (LC_ALL, "");
|
||||
/* m4 1.4.x does not want locale-aware decimal separators in the
|
||||
format builtin; easiest is to override the user's choice of
|
||||
LC_NUMERIC. */
|
||||
setlocale (LC_NUMERIC, "C");
|
||||
bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
textdomain (PACKAGE);
|
||||
atexit (close_stdin);
|
||||
|
||||
include_init ();
|
||||
debug_init ();
|
||||
|
||||
/* Stack overflow and program error handling. Ignore failure to
|
||||
install a handler, since this is merely for improved output on
|
||||
crash, and we should never crash ;). We install SIGBUS and
|
||||
SIGSEGV handlers prior to using the c-stack module; depending on
|
||||
the platform, c-stack will then override none, SIGSEGV, or both
|
||||
handlers. */
|
||||
program_error_message
|
||||
= xasprintf (_("internal error detected; please report this bug to <%s>"),
|
||||
PACKAGE_BUGREPORT);
|
||||
signal_message[SIGSEGV] = xstrdup (strsignal (SIGSEGV));
|
||||
signal_message[SIGABRT] = xstrdup (strsignal (SIGABRT));
|
||||
signal_message[SIGILL] = xstrdup (strsignal (SIGILL));
|
||||
signal_message[SIGFPE] = xstrdup (strsignal (SIGFPE));
|
||||
if (SIGBUS != SIGILL && SIGBUS != SIGSEGV)
|
||||
signal_message[SIGBUS] = xstrdup (strsignal (SIGBUS));
|
||||
sigemptyset (&act.sa_mask);
|
||||
/* One-shot - if we fault while handling a fault, we want to revert
|
||||
to default signal behavior. */
|
||||
act.sa_flags = SA_NODEFER | SA_RESETHAND;
|
||||
act.sa_handler = fault_handler;
|
||||
sigaction (SIGSEGV, &act, NULL);
|
||||
sigaction (SIGABRT, &act, NULL);
|
||||
sigaction (SIGILL, &act, NULL);
|
||||
sigaction (SIGFPE, &act, NULL);
|
||||
sigaction (SIGBUS, &act, NULL);
|
||||
if (c_stack_action (fault_handler) == 0)
|
||||
nesting_limit = 0;
|
||||
|
||||
#ifdef DEBUG_STKOVF
|
||||
/* Make it easier to test our fault handlers. Exporting M4_CRASH=0
|
||||
attempts a SIGSEGV, exporting it as 1 attempts an assertion
|
||||
failure with a fallback to abort. */
|
||||
{
|
||||
char *crash = getenv ("M4_CRASH");
|
||||
if (crash)
|
||||
{
|
||||
if (!strtol (crash, NULL, 10))
|
||||
++ * (int *) 8;
|
||||
assert (false);
|
||||
abort ();
|
||||
}
|
||||
}
|
||||
#endif /* DEBUG_STKOVF */
|
||||
|
||||
/* First, we decode the arguments, to size up tables and stuff. */
|
||||
head = tail = NULL;
|
||||
|
||||
while ((optchar = getopt_long (argc, (char **) argv, OPTSTRING,
|
||||
long_options, NULL)) != -1)
|
||||
switch (optchar)
|
||||
{
|
||||
default:
|
||||
usage (EXIT_FAILURE);
|
||||
|
||||
case 'B':
|
||||
case 'S':
|
||||
case 'T':
|
||||
/* Compatibility junk: options that other implementations
|
||||
support, but which we ignore as no-ops and don't list in
|
||||
--help. */
|
||||
error (0, 0,
|
||||
_("warning: `m4 -%c' may be removed in a future release"),
|
||||
optchar);
|
||||
break;
|
||||
|
||||
case 'N':
|
||||
case DIVERSIONS_OPTION:
|
||||
/* -N became an obsolete no-op in 1.4.x. */
|
||||
error (0, 0, _("warning: `m4 %s' is deprecated"),
|
||||
optchar == 'N' ? "-N" : "--diversions");
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
case 'U':
|
||||
case 's':
|
||||
case 't':
|
||||
case '\1':
|
||||
case DEBUGFILE_OPTION:
|
||||
/* Arguments that cannot be handled until later are accumulated. */
|
||||
|
||||
defn = (macro_definition *) xmalloc (sizeof (macro_definition));
|
||||
defn->code = optchar;
|
||||
defn->arg = optarg;
|
||||
defn->next = NULL;
|
||||
|
||||
if (head == NULL)
|
||||
head = defn;
|
||||
else
|
||||
tail->next = defn;
|
||||
tail = defn;
|
||||
|
||||
break;
|
||||
|
||||
case 'E':
|
||||
if (!fatal_warnings)
|
||||
fatal_warnings = true;
|
||||
else
|
||||
warning_status = EXIT_FAILURE;
|
||||
break;
|
||||
|
||||
case 'F':
|
||||
frozen_file_to_write = optarg;
|
||||
break;
|
||||
|
||||
case 'G':
|
||||
no_gnu_extensions = 1;
|
||||
break;
|
||||
|
||||
case 'H':
|
||||
hash_table_size = strtol (optarg, NULL, 10);
|
||||
if (hash_table_size == 0)
|
||||
hash_table_size = HASHMAX;
|
||||
break;
|
||||
|
||||
case 'I':
|
||||
add_include_directory (optarg);
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
nesting_limit = strtol (optarg, NULL, 10);
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
prefix_all_builtins = 1;
|
||||
break;
|
||||
|
||||
case 'Q':
|
||||
suppress_warnings = 1;
|
||||
break;
|
||||
|
||||
case 'R':
|
||||
frozen_file_to_read = optarg;
|
||||
break;
|
||||
|
||||
#ifdef ENABLE_CHANGEWORD
|
||||
case 'W':
|
||||
user_word_regexp = optarg;
|
||||
break;
|
||||
#endif
|
||||
|
||||
case 'd':
|
||||
debug_level = debug_decode (optarg);
|
||||
if (debug_level < 0)
|
||||
{
|
||||
error (0, 0, _("bad debug flags: `%s'"), optarg);
|
||||
debug_level = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
error (0, 0, _("warning: `m4 -e' is deprecated, use `-i' instead"));
|
||||
FALLTHROUGH;
|
||||
case 'i':
|
||||
interactive = true;
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
no_gnu_extensions = 0;
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
max_debug_argument_length = strtol (optarg, NULL, 10);
|
||||
if (max_debug_argument_length <= 0)
|
||||
max_debug_argument_length = 0;
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
/* -o/--error-output are deprecated synonyms of --debugfile,
|
||||
but don't issue a deprecation warning until autoconf 2.61
|
||||
or later is more widely established, as such a warning
|
||||
would interfere with all earlier versions of autoconf. */
|
||||
/* Don't call debug_set_output here, as it has side effects. */
|
||||
debugfile = optarg;
|
||||
break;
|
||||
|
||||
case WARN_MACRO_SEQUENCE_OPTION:
|
||||
/* Don't call set_macro_sequence here, as it can exit.
|
||||
--warn-macro-sequence sets optarg to NULL (which uses the
|
||||
default regexp); --warn-macro-sequence= sets optarg to ""
|
||||
(which disables these warnings). */
|
||||
macro_sequence = optarg;
|
||||
break;
|
||||
|
||||
case VERSION_OPTION:
|
||||
version_etc (stdout, PACKAGE, PACKAGE_NAME, VERSION, AUTHORS, NULL);
|
||||
exit (EXIT_SUCCESS);
|
||||
break;
|
||||
|
||||
case HELP_OPTION:
|
||||
usage (EXIT_SUCCESS);
|
||||
break;
|
||||
}
|
||||
|
||||
defines = head;
|
||||
|
||||
/* Do the basic initializations. */
|
||||
if (debugfile && !debug_set_output (debugfile))
|
||||
M4ERROR ((warning_status, errno, _("cannot set debug file `%s'"),
|
||||
debugfile));
|
||||
|
||||
input_init ();
|
||||
output_init ();
|
||||
symtab_init ();
|
||||
set_macro_sequence (macro_sequence);
|
||||
include_env_init ();
|
||||
|
||||
if (frozen_file_to_read)
|
||||
reload_frozen_state (frozen_file_to_read);
|
||||
else
|
||||
builtin_init ();
|
||||
|
||||
/* Interactive mode means unbuffered output, and interrupts ignored. */
|
||||
|
||||
if (interactive)
|
||||
{
|
||||
signal (SIGINT, SIG_IGN);
|
||||
setbuf (stdout, (char *) NULL);
|
||||
}
|
||||
|
||||
/* Handle deferred command line macro definitions. Must come after
|
||||
initialization of the symbol table. */
|
||||
|
||||
while (defines != NULL)
|
||||
{
|
||||
macro_definition *next;
|
||||
symbol *sym;
|
||||
|
||||
switch (defines->code)
|
||||
{
|
||||
case 'D':
|
||||
{
|
||||
/* defines->arg is read-only, so we need a copy. */
|
||||
char *macro_name = xstrdup (defines->arg);
|
||||
char *macro_value = strchr (macro_name, '=');
|
||||
if (macro_value)
|
||||
*macro_value++ = '\0';
|
||||
define_user_macro (macro_name, strlen (macro_name), macro_value,
|
||||
macro_value ? strlen (macro_value) : 0,
|
||||
SYMBOL_INSERT);
|
||||
free (macro_name);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'U':
|
||||
lookup_symbol (defines->arg, strlen (defines->arg), SYMBOL_DELETE);
|
||||
break;
|
||||
|
||||
case 't':
|
||||
sym = lookup_symbol (defines->arg, strlen (defines->arg),
|
||||
SYMBOL_INSERT);
|
||||
SYMBOL_TRACED (sym) = true;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
sync_output = 1;
|
||||
break;
|
||||
|
||||
case '\1':
|
||||
seen_file = true;
|
||||
process_file (defines->arg);
|
||||
break;
|
||||
|
||||
case DEBUGFILE_OPTION:
|
||||
if (!debug_set_output (defines->arg))
|
||||
M4ERROR ((warning_status, errno, _("cannot set debug file `%s'"),
|
||||
debugfile ? debugfile : _("stderr")));
|
||||
break;
|
||||
|
||||
default:
|
||||
M4ERROR ((0, 0, "INTERNAL ERROR: bad code in deferred arguments"));
|
||||
abort ();
|
||||
}
|
||||
|
||||
next = defines->next;
|
||||
free (defines);
|
||||
defines = next;
|
||||
}
|
||||
|
||||
/* Handle remaining input files. Each file is pushed on the input,
|
||||
and the input read. Wrapup text is handled separately later. */
|
||||
|
||||
if (optind == argc && !seen_file)
|
||||
process_file ("-");
|
||||
else
|
||||
for (; optind < argc; optind++)
|
||||
process_file (argv[optind]);
|
||||
|
||||
/* Now handle wrapup text. */
|
||||
|
||||
while (pop_wrapup ())
|
||||
expand_input ();
|
||||
|
||||
/* Change debug stream back to stderr, to force flushing the debug
|
||||
stream and detect any errors it might have encountered. The
|
||||
three standard streams are closed by close_stdin. */
|
||||
debug_set_output (NULL);
|
||||
|
||||
if (frozen_file_to_write)
|
||||
produce_frozen_state (frozen_file_to_write);
|
||||
else
|
||||
{
|
||||
make_diversion (0);
|
||||
undivert_all ();
|
||||
}
|
||||
output_exit ();
|
||||
free_macro_sequence ();
|
||||
exit (retcode);
|
||||
}
|
||||
@@ -0,0 +1,519 @@
|
||||
/* GNU m4 -- A simple macro processor
|
||||
|
||||
Copyright (C) 1989-1994, 2004-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/>.
|
||||
*/
|
||||
|
||||
/* We use <config.h> instead of "config.h" so that a compilation
|
||||
using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h
|
||||
(which it would do because it found this file in $srcdir). */
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <c-ctype.h>
|
||||
#include <errno.h>
|
||||
#include <error.h>
|
||||
#include <limits.h>
|
||||
#include <locale.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "attribute.h"
|
||||
#include "binary-io.h"
|
||||
#include "clean-temp.h"
|
||||
#include "cloexec.h"
|
||||
#include "close-stream.h"
|
||||
#include "closein.h"
|
||||
#include "dirname.h"
|
||||
#include "exitfail.h"
|
||||
#include "filenamecat.h"
|
||||
#include "intprops.h"
|
||||
#include "obstack.h"
|
||||
#include "stdio--.h"
|
||||
#include "stdlib--.h"
|
||||
#include "unistd--.h"
|
||||
#include "verify.h"
|
||||
#include "xalloc.h"
|
||||
#include "xmemdup0.h"
|
||||
#include "xprintf.h"
|
||||
#include "xvasprintf.h"
|
||||
|
||||
/* Canonicalize UNIX recognition macros. */
|
||||
#if defined unix || defined __unix || defined __unix__ \
|
||||
|| defined _POSIX_VERSION || defined _POSIX2_VERSION \
|
||||
|| defined __NetBSD__ || defined __OpenBSD__ \
|
||||
|| defined __APPLE__ || defined __APPLE_CC__
|
||||
# define UNIX 1
|
||||
#endif
|
||||
|
||||
/* Canonicalize Windows recognition macros. */
|
||||
#if (defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__
|
||||
# define W32_NATIVE 1
|
||||
#endif
|
||||
|
||||
/* Canonicalize OS/2 recognition macro. */
|
||||
#ifdef __EMX__
|
||||
# define OS2 1
|
||||
# undef UNIX
|
||||
#endif
|
||||
|
||||
/* Used if any programmer error is detected (not possible, right?) */
|
||||
#define EXIT_INTERNAL_ERROR 2
|
||||
|
||||
/* Used for version mismatch, when -R detects a frozen file it can't parse. */
|
||||
#define EXIT_MISMATCH 63
|
||||
|
||||
/* NLS. */
|
||||
|
||||
#include "gettext.h"
|
||||
#if ! ENABLE_NLS
|
||||
# undef textdomain
|
||||
# define textdomain(Domainname) /* empty */
|
||||
# undef bindtextdomain
|
||||
# define bindtextdomain(Domainname, Dirname) /* empty */
|
||||
#endif
|
||||
|
||||
#define _(msgid) gettext (msgid)
|
||||
|
||||
/* Various declarations. */
|
||||
|
||||
struct string
|
||||
{
|
||||
char *string; /* characters of the string */
|
||||
size_t length; /* length of the string */
|
||||
};
|
||||
typedef struct string STRING;
|
||||
|
||||
/* Memory allocation. */
|
||||
#define obstack_chunk_alloc xmalloc
|
||||
#define obstack_chunk_free free
|
||||
|
||||
/* Those must come first. */
|
||||
typedef struct token_data token_data;
|
||||
typedef void builtin_func (struct obstack *, int, token_data **);
|
||||
|
||||
/* Gnulib's stdbool doesn't work with bool bitfields. For nicer
|
||||
debugging, use bool when we know it works, but use the more
|
||||
portable unsigned int elsewhere. */
|
||||
#if _GL_GNUC_PREREQ (2, 0)
|
||||
typedef bool bool_bitfield;
|
||||
#else
|
||||
typedef unsigned int bool_bitfield;
|
||||
#endif /* ! __GNUC__ */
|
||||
|
||||
/* File: m4.c --- global definitions. */
|
||||
|
||||
/* Option flags. */
|
||||
extern int sync_output; /* -s */
|
||||
extern int debug_level; /* -d */
|
||||
extern size_t hash_table_size; /* -H */
|
||||
extern int no_gnu_extensions; /* -G */
|
||||
extern int prefix_all_builtins; /* -P */
|
||||
extern int max_debug_argument_length; /* -l */
|
||||
extern int suppress_warnings; /* -Q */
|
||||
extern int warning_status; /* -E */
|
||||
extern int nesting_limit; /* -L */
|
||||
#ifdef ENABLE_CHANGEWORD
|
||||
extern const char *user_word_regexp; /* -W */
|
||||
#endif
|
||||
|
||||
/* Error handling. */
|
||||
extern int retcode;
|
||||
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
extern void m4_error (int, int, const char *, ...)
|
||||
ATTRIBUTE_COLD ATTRIBUTE_FORMAT ((__printf__, 3, 4));
|
||||
extern void m4_error_at_line (int, int, const char *, int, const char *, ...)
|
||||
ATTRIBUTE_COLD ATTRIBUTE_FORMAT ((__printf__, 5, 6));
|
||||
extern _Noreturn void m4_failure (int, const char *, ...)
|
||||
ATTRIBUTE_FORMAT ((__printf__, 2, 3));
|
||||
extern _Noreturn void m4_failure_at_line (int, const char *, int,
|
||||
const char *, ...)
|
||||
ATTRIBUTE_FORMAT ((__printf__, 4, 5));
|
||||
/* *INDENT-ON* */
|
||||
|
||||
#define M4ERROR(Arglist) (m4_error Arglist)
|
||||
#define M4ERROR_AT_LINE(Arglist) (m4_error_at_line Arglist)
|
||||
|
||||
|
||||
/* File: debug.c --- debugging and tracing function. */
|
||||
|
||||
extern FILE *debug;
|
||||
|
||||
/* The value of debug_level is a bitmask of the following. */
|
||||
|
||||
/* a: show arglist in trace output */
|
||||
#define DEBUG_TRACE_ARGS 1
|
||||
/* e: show expansion in trace output */
|
||||
#define DEBUG_TRACE_EXPANSION 2
|
||||
/* q: quote args and expansion in trace output */
|
||||
#define DEBUG_TRACE_QUOTE 4
|
||||
/* t: trace all macros -- overrides trace{on,off} */
|
||||
#define DEBUG_TRACE_ALL 8
|
||||
/* l: add line numbers to trace output */
|
||||
#define DEBUG_TRACE_LINE 16
|
||||
/* f: add file name to trace output */
|
||||
#define DEBUG_TRACE_FILE 32
|
||||
/* p: trace path search of include files */
|
||||
#define DEBUG_TRACE_PATH 64
|
||||
/* c: show macro call before args collection */
|
||||
#define DEBUG_TRACE_CALL 128
|
||||
/* i: trace changes of input files */
|
||||
#define DEBUG_TRACE_INPUT 256
|
||||
/* x: add call id to trace output */
|
||||
#define DEBUG_TRACE_CALLID 512
|
||||
|
||||
/* V: very verbose -- print everything */
|
||||
#define DEBUG_TRACE_VERBOSE 1023
|
||||
/* default flags -- equiv: aeq */
|
||||
#define DEBUG_TRACE_DEFAULT 7
|
||||
|
||||
#define DEBUG_PRINT1(Fmt, Arg1) \
|
||||
do \
|
||||
{ \
|
||||
if (debug != NULL) \
|
||||
xfprintf (debug, Fmt, Arg1); \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
#define DEBUG_PRINT3(Fmt, Arg1, Arg2, Arg3) \
|
||||
do \
|
||||
{ \
|
||||
if (debug != NULL) \
|
||||
xfprintf (debug, Fmt, Arg1, Arg2, Arg3); \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
#define DEBUG_MESSAGE(Fmt) \
|
||||
do \
|
||||
{ \
|
||||
if (debug != NULL) \
|
||||
{ \
|
||||
debug_message_prefix (); \
|
||||
xfprintf (debug, Fmt); \
|
||||
putc ('\n', debug); \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
#define DEBUG_MESSAGE1(Fmt, Arg1) \
|
||||
do \
|
||||
{ \
|
||||
if (debug != NULL) \
|
||||
{ \
|
||||
debug_message_prefix (); \
|
||||
xfprintf (debug, Fmt, Arg1); \
|
||||
putc ('\n', debug); \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
#define DEBUG_MESSAGE2(Fmt, Arg1, Arg2) \
|
||||
do \
|
||||
{ \
|
||||
if (debug != NULL) \
|
||||
{ \
|
||||
debug_message_prefix (); \
|
||||
xfprintf (debug, Fmt, Arg1, Arg2); \
|
||||
putc ('\n', debug); \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
extern void debug_init (void);
|
||||
extern int debug_decode (const char *);
|
||||
extern void debug_flush_files (void);
|
||||
extern bool debug_set_output (const char *);
|
||||
extern void debug_message_prefix (void);
|
||||
|
||||
extern void trace_prepre (const char *, int);
|
||||
extern void trace_pre (const char *, int, int, token_data **);
|
||||
extern void trace_post (const char *, int, int, const char *);
|
||||
|
||||
/* File: input.c --- lexical definitions. */
|
||||
|
||||
/* Various different token types. */
|
||||
enum token_type
|
||||
{
|
||||
TOKEN_EOF, /* end of file */
|
||||
TOKEN_STRING, /* a quoted string or comment */
|
||||
TOKEN_WORD, /* an identifier */
|
||||
TOKEN_OPEN, /* ( */
|
||||
TOKEN_COMMA, /* , */
|
||||
TOKEN_CLOSE, /* ) */
|
||||
TOKEN_SIMPLE, /* any other single character */
|
||||
TOKEN_MACDEF /* a macro's definition (see "defn") */
|
||||
};
|
||||
|
||||
/* The data for a token, a macro argument, and a macro definition. */
|
||||
enum token_data_type
|
||||
{
|
||||
TOKEN_VOID,
|
||||
TOKEN_TEXT,
|
||||
TOKEN_FUNC
|
||||
};
|
||||
|
||||
struct token_data
|
||||
{
|
||||
enum token_data_type type;
|
||||
/* Several places in the code only work with tokens no larger than
|
||||
2G. Although len only matters for a text token, putting it here
|
||||
instead of in the union allows struct token_data to be
|
||||
smaller. */
|
||||
int len;
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
char *text;
|
||||
#ifdef ENABLE_CHANGEWORD
|
||||
char *original_text;
|
||||
#endif
|
||||
}
|
||||
u_t;
|
||||
builtin_func *func;
|
||||
}
|
||||
u;
|
||||
};
|
||||
|
||||
#define TOKEN_DATA_TYPE(Td) ((Td)->type)
|
||||
#define TOKEN_DATA_LEN(Td) ((Td)->len)
|
||||
#define TOKEN_DATA_TEXT(Td) ((Td)->u.u_t.text)
|
||||
#ifdef ENABLE_CHANGEWORD
|
||||
# define TOKEN_DATA_ORIG_TEXT(Td) ((Td)->u.u_t.original_text)
|
||||
#endif
|
||||
#define TOKEN_DATA_FUNC(Td) ((Td)->u.func)
|
||||
|
||||
typedef enum token_type token_type;
|
||||
typedef enum token_data_type token_data_type;
|
||||
|
||||
extern void input_init (void);
|
||||
extern token_type peek_token (void);
|
||||
extern token_type next_token (token_data *, int *);
|
||||
extern void skip_line (void);
|
||||
|
||||
/* push back input */
|
||||
extern void push_file (FILE *, const char *, bool);
|
||||
extern void push_macro (builtin_func *);
|
||||
extern struct obstack *push_string_init (void);
|
||||
extern const char *push_string_finish (void);
|
||||
extern void push_wrapup (const char *);
|
||||
extern bool pop_wrapup (void);
|
||||
|
||||
/* current input file, and line */
|
||||
extern const char *current_file;
|
||||
extern int current_line;
|
||||
|
||||
/* left and right quote, begin and end comment */
|
||||
extern STRING bcomm;
|
||||
extern STRING ecomm;
|
||||
extern STRING lquote;
|
||||
extern STRING rquote;
|
||||
|
||||
#define DEF_LQUOTE "`"
|
||||
#define DEF_RQUOTE "\'"
|
||||
#define DEF_BCOMM "#"
|
||||
#define DEF_ECOMM "\n"
|
||||
|
||||
extern void set_quotes (const char *, const char *);
|
||||
extern void set_comment (const char *, const char *);
|
||||
#ifdef ENABLE_CHANGEWORD
|
||||
extern void set_word_regexp (const char *);
|
||||
#endif
|
||||
|
||||
/* File: output.c --- output functions. */
|
||||
extern int current_diversion;
|
||||
extern int output_current_line;
|
||||
|
||||
extern void output_init (void);
|
||||
extern void output_exit (void);
|
||||
extern void output_text (const char *, int);
|
||||
extern void shipout_text (struct obstack *, const char *, int, int);
|
||||
extern void make_diversion (int);
|
||||
extern void insert_diversion (int);
|
||||
extern void insert_file (FILE *);
|
||||
extern void freeze_diversions (FILE *);
|
||||
|
||||
/* File symtab.c --- symbol table definitions. */
|
||||
|
||||
/* Operation modes for lookup_symbol (). */
|
||||
enum symbol_lookup
|
||||
{
|
||||
SYMBOL_LOOKUP,
|
||||
SYMBOL_INSERT,
|
||||
SYMBOL_DELETE,
|
||||
SYMBOL_PUSHDEF,
|
||||
SYMBOL_POPDEF
|
||||
};
|
||||
|
||||
/* Symbol table entry. */
|
||||
struct symbol
|
||||
{
|
||||
struct symbol *stack; /* pushdef stack */
|
||||
struct symbol *next; /* hash bucket chain */
|
||||
bool_bitfield traced:1;
|
||||
bool_bitfield macro_args:1;
|
||||
bool_bitfield blind_no_args:1;
|
||||
bool_bitfield deleted:1;
|
||||
int pending_expansions;
|
||||
|
||||
size_t hash;
|
||||
char *name;
|
||||
int namelen;
|
||||
token_data data;
|
||||
};
|
||||
|
||||
#define SYMBOL_STACK(S) ((S)->stack)
|
||||
#define SYMBOL_TRACED(S) ((S)->traced)
|
||||
#define SYMBOL_MACRO_ARGS(S) ((S)->macro_args)
|
||||
#define SYMBOL_BLIND_NO_ARGS(S) ((S)->blind_no_args)
|
||||
#define SYMBOL_DELETED(S) ((S)->deleted)
|
||||
#define SYMBOL_PENDING_EXPANSIONS(S) ((S)->pending_expansions)
|
||||
#define SYMBOL_NAME(S) ((S)->name)
|
||||
#define SYMBOL_NAME_LEN(S) ((S)->namelen)
|
||||
#define SYMBOL_TYPE(S) (TOKEN_DATA_TYPE (&(S)->data))
|
||||
#define SYMBOL_TEXT(S) (TOKEN_DATA_TEXT (&(S)->data))
|
||||
#define SYMBOL_TEXT_LEN(S) (TOKEN_DATA_LEN (&(S)->data))
|
||||
#define SYMBOL_FUNC(S) (TOKEN_DATA_FUNC (&(S)->data))
|
||||
|
||||
typedef enum symbol_lookup symbol_lookup;
|
||||
typedef struct symbol symbol;
|
||||
typedef void hack_symbol (symbol *, void *);
|
||||
|
||||
#define HASHMAX 65537 /* default, overridden by -Hsize */
|
||||
|
||||
extern void free_symbol (symbol * sym);
|
||||
extern void symtab_init (void);
|
||||
extern symbol *lookup_symbol (const char *, int, symbol_lookup);
|
||||
extern void hack_all_symbols (hack_symbol *, void *);
|
||||
|
||||
/* File: macro.c --- macro expansion. */
|
||||
|
||||
extern int expansion_level;
|
||||
|
||||
extern void expand_input (void);
|
||||
extern void call_macro (symbol *, int, token_data **, struct obstack *);
|
||||
|
||||
/* File: builtin.c --- builtins. */
|
||||
|
||||
struct builtin
|
||||
{
|
||||
const char *name;
|
||||
bool_bitfield gnu_extension:1;
|
||||
bool_bitfield groks_macro_args:1;
|
||||
bool_bitfield blind_if_no_args:1;
|
||||
builtin_func *func;
|
||||
};
|
||||
|
||||
struct predefined
|
||||
{
|
||||
const char *unix_name;
|
||||
const char *gnu_name;
|
||||
const char *func;
|
||||
};
|
||||
|
||||
typedef struct builtin builtin;
|
||||
typedef struct predefined predefined;
|
||||
struct re_pattern_buffer;
|
||||
struct re_registers;
|
||||
|
||||
/* The default sequence detects multi-digit parameters (obsolete after
|
||||
1.4.x), and any use of extended arguments with the default ${}
|
||||
syntax (new in 2.0). */
|
||||
#define DEFAULT_MACRO_SEQUENCE "\\$\\({[^}]*}\\|[0-9][0-9]+\\)"
|
||||
|
||||
extern void builtin_init (void);
|
||||
extern void define_builtin (const char *, const builtin *, symbol_lookup);
|
||||
extern void set_macro_sequence (const char *);
|
||||
extern void free_macro_sequence (void);
|
||||
extern void define_user_macro (const char *, int, const char *, size_t,
|
||||
symbol_lookup);
|
||||
extern void undivert_all (void);
|
||||
extern void expand_user_macro (struct obstack *, symbol *, int,
|
||||
token_data **);
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
extern void m4_placeholder (struct obstack *, int, token_data **)
|
||||
ATTRIBUTE_COLD;
|
||||
/* *INDENT-ON* */
|
||||
|
||||
extern void init_pattern_buffer (struct re_pattern_buffer *,
|
||||
struct re_registers *);
|
||||
extern const char *ntoa (int32_t, int, const char **);
|
||||
|
||||
extern const builtin *find_builtin_by_addr (builtin_func *);
|
||||
extern const builtin *find_builtin_by_name (const char *);
|
||||
|
||||
/* File: path.c --- path search for include files. */
|
||||
|
||||
extern void include_init (void);
|
||||
extern void include_env_init (void);
|
||||
extern void add_include_directory (const char *);
|
||||
extern FILE *m4_path_search (const char *, bool, char **);
|
||||
|
||||
/* File: eval.c --- expression evaluation. */
|
||||
|
||||
extern bool evaluate (const char *, int32_t *);
|
||||
|
||||
/* File: format.c --- printf like formatting. */
|
||||
|
||||
extern void expand_format (struct obstack *, int, token_data **);
|
||||
|
||||
/* File: freeze.c --- frozen state files. */
|
||||
|
||||
extern void produce_frozen_state (const char *);
|
||||
extern void reload_frozen_state (const char *);
|
||||
|
||||
/* Debugging the memory allocator. */
|
||||
|
||||
#ifdef WITH_DMALLOC
|
||||
# define DMALLOC_FUNC_CHECK
|
||||
# include <dmalloc.h>
|
||||
#endif
|
||||
|
||||
/* Other debug stuff. */
|
||||
|
||||
#ifdef DEBUG
|
||||
# define DEBUG_INCL 1
|
||||
# define DEBUG_INPUT 1
|
||||
# define DEBUG_MACRO 1
|
||||
# define DEBUG_OUTPUT 1
|
||||
# define DEBUG_STKOVF 1
|
||||
# define DEBUG_SYM 1
|
||||
#endif
|
||||
|
||||
/* Convert a possibly-signed character to an unsigned character. This is
|
||||
a bit safer than casting to unsigned char, since it catches some type
|
||||
errors that the cast doesn't. */
|
||||
#if HAVE_INLINE
|
||||
static inline unsigned char
|
||||
to_uchar (char ch)
|
||||
{
|
||||
return ch;
|
||||
}
|
||||
#else
|
||||
# define to_uchar(C) ((unsigned char) (C))
|
||||
#endif
|
||||
|
||||
/* Avoid negative logic when comparing two strings. */
|
||||
#define STREQ(a, b) (strcmp (a, b) == 0)
|
||||
@@ -0,0 +1,405 @@
|
||||
/* GNU m4 -- A simple macro processor
|
||||
|
||||
Copyright (C) 1989-1994, 2006-2007, 2009-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/>.
|
||||
*/
|
||||
|
||||
/* This file contains the functions, that performs the basic argument
|
||||
parsing and macro expansion. */
|
||||
|
||||
#include "m4.h"
|
||||
|
||||
static void expand_macro (symbol *);
|
||||
static void expand_token (struct obstack *, token_type, token_data *, int);
|
||||
|
||||
/* Current recursion level in expand_macro (). */
|
||||
int expansion_level = 0;
|
||||
|
||||
/* The number of the current call of expand_macro (). */
|
||||
static int macro_call_id = 0;
|
||||
|
||||
/* The shared stack of collected arguments for macro calls; as each
|
||||
argument is collected, it is finished and its location stored in
|
||||
argv_stack. Normally, this stack can be used simultaneously by
|
||||
multiple macro calls; the exception is when an outer macro has
|
||||
generated some text, then calls a nested macro, in which case the
|
||||
nested macro must use a local stack to leave the unfinished text
|
||||
alone. Too bad obstack.h does not provide an easy way to reopen a
|
||||
finished object for further growth, but in practice this does not
|
||||
hurt us too much. */
|
||||
static struct obstack argc_stack;
|
||||
|
||||
/* The shared stack of pointers to collected arguments for macro
|
||||
calls. This object is never finished; we exploit the fact that
|
||||
obstack_blank_fast is documented to take a negative size to reduce
|
||||
the size again. */
|
||||
static struct obstack argv_stack;
|
||||
|
||||
/*----------------------------------------------------------------------.
|
||||
| This function read all input, and expands each token, one at a time. |
|
||||
`----------------------------------------------------------------------*/
|
||||
|
||||
void
|
||||
expand_input (void)
|
||||
{
|
||||
token_type t;
|
||||
token_data td;
|
||||
int line;
|
||||
|
||||
obstack_init (&argc_stack);
|
||||
obstack_init (&argv_stack);
|
||||
|
||||
while ((t = next_token (&td, &line)) != TOKEN_EOF)
|
||||
expand_token ((struct obstack *) NULL, t, &td, line);
|
||||
|
||||
obstack_free (&argc_stack, NULL);
|
||||
obstack_free (&argv_stack, NULL);
|
||||
}
|
||||
|
||||
|
||||
/*----------------------------------------------------------------.
|
||||
| Expand one token, according to its type. Potential macro names |
|
||||
| (TOKEN_WORD) are looked up in the symbol table, to see if they |
|
||||
| have a macro definition. If they have, they are expanded as |
|
||||
| macros, otherwise the text is just copied to the output. |
|
||||
`----------------------------------------------------------------*/
|
||||
|
||||
static void
|
||||
expand_token (struct obstack *obs, token_type t, token_data *td, int line)
|
||||
{
|
||||
symbol *sym;
|
||||
|
||||
switch (t)
|
||||
{ /* TOKSW */
|
||||
case TOKEN_EOF:
|
||||
case TOKEN_MACDEF:
|
||||
break;
|
||||
|
||||
case TOKEN_OPEN:
|
||||
case TOKEN_COMMA:
|
||||
case TOKEN_CLOSE:
|
||||
case TOKEN_SIMPLE:
|
||||
case TOKEN_STRING:
|
||||
shipout_text (obs, TOKEN_DATA_TEXT (td), strlen (TOKEN_DATA_TEXT (td)),
|
||||
line);
|
||||
break;
|
||||
|
||||
case TOKEN_WORD:
|
||||
sym = lookup_symbol (TOKEN_DATA_TEXT (td), TOKEN_DATA_LEN (td),
|
||||
SYMBOL_LOOKUP);
|
||||
if (sym == NULL || SYMBOL_TYPE (sym) == TOKEN_VOID
|
||||
|| (SYMBOL_TYPE (sym) == TOKEN_FUNC
|
||||
&& SYMBOL_BLIND_NO_ARGS (sym) && peek_token () != TOKEN_OPEN))
|
||||
{
|
||||
#ifdef ENABLE_CHANGEWORD
|
||||
shipout_text (obs, TOKEN_DATA_ORIG_TEXT (td),
|
||||
strlen (TOKEN_DATA_ORIG_TEXT (td)), line);
|
||||
#else
|
||||
shipout_text (obs, TOKEN_DATA_TEXT (td),
|
||||
strlen (TOKEN_DATA_TEXT (td)), line);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
expand_macro (sym);
|
||||
break;
|
||||
|
||||
default:
|
||||
M4ERROR ((warning_status, 0,
|
||||
"INTERNAL ERROR: bad token type in expand_token ()"));
|
||||
abort ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------.
|
||||
| This function parses one argument to a macro call. It expects the |
|
||||
| first left parenthesis, or the separating comma, to have been read |
|
||||
| by the caller. It skips leading whitespace, and reads and expands |
|
||||
| tokens, until it finds a comma or an right parenthesis at the same |
|
||||
| level of parentheses. It returns a flag indicating whether the |
|
||||
| argument read is the last for the active macro call. The argument |
|
||||
| is built on the obstack OBS, indirectly through expand_token (). |
|
||||
`-------------------------------------------------------------------*/
|
||||
|
||||
static bool
|
||||
expand_argument (struct obstack *obs, token_data *argp, bool groks_macro)
|
||||
{
|
||||
token_type t;
|
||||
token_data td;
|
||||
char *text;
|
||||
int paren_level;
|
||||
const char *file = current_file;
|
||||
int line = current_line;
|
||||
size_t len;
|
||||
|
||||
TOKEN_DATA_TYPE (argp) = TOKEN_VOID;
|
||||
|
||||
/* Skip leading white space. */
|
||||
do
|
||||
{
|
||||
t = next_token (&td, NULL);
|
||||
}
|
||||
while (t == TOKEN_SIMPLE && c_isspace (*TOKEN_DATA_TEXT (&td)));
|
||||
|
||||
paren_level = 0;
|
||||
|
||||
while (1)
|
||||
{
|
||||
|
||||
switch (t)
|
||||
{ /* TOKSW */
|
||||
case TOKEN_COMMA:
|
||||
case TOKEN_CLOSE:
|
||||
if (paren_level == 0)
|
||||
{
|
||||
/* The argument MUST be finished, whether we want it or not. */
|
||||
obstack_1grow (obs, '\0');
|
||||
len = obstack_object_size (obs) - 1;
|
||||
text = (char *) obstack_finish (obs);
|
||||
|
||||
if (TOKEN_DATA_TYPE (argp) == TOKEN_FUNC && len)
|
||||
{
|
||||
M4ERROR ((warning_status, 0,
|
||||
_("Warning: cannot concatenate builtin tokens")));
|
||||
TOKEN_DATA_TYPE (argp) = TOKEN_VOID;
|
||||
}
|
||||
if (TOKEN_DATA_TYPE (argp) == TOKEN_VOID)
|
||||
{
|
||||
TOKEN_DATA_TYPE (argp) = TOKEN_TEXT;
|
||||
TOKEN_DATA_TEXT (argp) = text;
|
||||
TOKEN_DATA_LEN (argp) = len;
|
||||
}
|
||||
return t == TOKEN_COMMA;
|
||||
}
|
||||
FALLTHROUGH;
|
||||
case TOKEN_OPEN:
|
||||
case TOKEN_SIMPLE:
|
||||
text = TOKEN_DATA_TEXT (&td);
|
||||
|
||||
if (*text == '(')
|
||||
paren_level++;
|
||||
else if (*text == ')')
|
||||
paren_level--;
|
||||
expand_token (obs, t, &td, line);
|
||||
break;
|
||||
|
||||
case TOKEN_EOF:
|
||||
/* current_file changed to "" if we see TOKEN_EOF, use the
|
||||
previous value we stored earlier. */
|
||||
m4_failure_at_line (0, file, line,
|
||||
_("ERROR: end of file in argument list"));
|
||||
|
||||
case TOKEN_WORD:
|
||||
case TOKEN_STRING:
|
||||
expand_token (obs, t, &td, line);
|
||||
break;
|
||||
|
||||
case TOKEN_MACDEF:
|
||||
/* Silently ignore macro tokens outside of certain builtins */
|
||||
if (groks_macro)
|
||||
{
|
||||
if (obstack_object_size (obs) == 0 &&
|
||||
TOKEN_DATA_TYPE (argp) == TOKEN_VOID)
|
||||
{
|
||||
TOKEN_DATA_TYPE (argp) = TOKEN_FUNC;
|
||||
TOKEN_DATA_FUNC (argp) = TOKEN_DATA_FUNC (&td);
|
||||
}
|
||||
else
|
||||
{
|
||||
M4ERROR ((warning_status, 0,
|
||||
_("Warning: cannot concatenate builtin tokens")));
|
||||
TOKEN_DATA_TYPE (argp) = TOKEN_VOID;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
M4ERROR ((warning_status, 0,
|
||||
"INTERNAL ERROR: bad token type in expand_argument ()"));
|
||||
abort ();
|
||||
}
|
||||
|
||||
t = next_token (&td, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------.
|
||||
| Collect all the arguments to a call of the macro SYM. The |
|
||||
| arguments are stored on the obstack ARGUMENTS and a table of |
|
||||
| pointers to the arguments on the obstack ARGPTR. |
|
||||
`-------------------------------------------------------------*/
|
||||
|
||||
static void
|
||||
collect_arguments (symbol *sym, struct obstack *argptr,
|
||||
struct obstack *arguments)
|
||||
{
|
||||
token_data td;
|
||||
token_data *tdp;
|
||||
bool more_args;
|
||||
bool groks_macro = SYMBOL_MACRO_ARGS (sym);
|
||||
|
||||
TOKEN_DATA_TYPE (&td) = TOKEN_TEXT;
|
||||
TOKEN_DATA_TEXT (&td) = SYMBOL_NAME (sym);
|
||||
TOKEN_DATA_LEN (&td) = SYMBOL_NAME_LEN (sym);
|
||||
tdp = (token_data *) obstack_copy (arguments, &td, sizeof td);
|
||||
obstack_ptr_grow (argptr, tdp);
|
||||
|
||||
if (peek_token () == TOKEN_OPEN)
|
||||
{
|
||||
next_token (&td, NULL); /* gobble parenthesis */
|
||||
do
|
||||
{
|
||||
more_args = expand_argument (arguments, &td, groks_macro);
|
||||
tdp = (token_data *) obstack_copy (arguments, &td, sizeof td);
|
||||
obstack_ptr_grow (argptr, tdp);
|
||||
}
|
||||
while (more_args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------.
|
||||
| The actual call of a macro is handled by call_macro (). |
|
||||
| call_macro () is passed a symbol SYM, whose type is used to call |
|
||||
| either a builtin function, or the user macro expansion function |
|
||||
| expand_user_macro () (lives in builtin.c). There are ARGC |
|
||||
| arguments to the call, stored in the ARGV table. The expansion is |
|
||||
| left on the obstack EXPANSION. Macro tracing is also handled |
|
||||
| here. |
|
||||
`-------------------------------------------------------------------*/
|
||||
|
||||
void
|
||||
call_macro (symbol *sym, int argc, token_data **argv,
|
||||
struct obstack *expansion)
|
||||
{
|
||||
switch (SYMBOL_TYPE (sym))
|
||||
{
|
||||
case TOKEN_FUNC:
|
||||
(*SYMBOL_FUNC (sym)) (expansion, argc, argv);
|
||||
break;
|
||||
|
||||
case TOKEN_TEXT:
|
||||
expand_user_macro (expansion, sym, argc, argv);
|
||||
break;
|
||||
|
||||
case TOKEN_VOID:
|
||||
default:
|
||||
M4ERROR ((warning_status, 0,
|
||||
"INTERNAL ERROR: bad symbol type in call_macro ()"));
|
||||
abort ();
|
||||
}
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------.
|
||||
| The macro expansion is handled by expand_macro (). It parses the |
|
||||
| arguments, using collect_arguments (), and builds a table of |
|
||||
| pointers to the arguments. The arguments themselves are stored on |
|
||||
| a local obstack. Expand_macro () uses call_macro () to do the |
|
||||
| call of the macro. |
|
||||
| |
|
||||
| Expand_macro () is potentially recursive, since it calls |
|
||||
| expand_argument (), which might call expand_token (), which might |
|
||||
| call expand_macro (). |
|
||||
`-------------------------------------------------------------------*/
|
||||
|
||||
static void
|
||||
expand_macro (symbol *sym)
|
||||
{
|
||||
struct obstack arguments; /* Alternate obstack if argc_stack is busy. */
|
||||
unsigned argv_base; /* Size of argv_stack on entry. */
|
||||
bool use_argc_stack = true; /* Whether argc_stack is safe. */
|
||||
token_data **argv;
|
||||
int argc;
|
||||
struct obstack *expansion;
|
||||
const char *expanded;
|
||||
bool traced;
|
||||
int my_call_id;
|
||||
|
||||
/* Report errors at the location where the open parenthesis (if any)
|
||||
was found, but after expansion, restore global state back to the
|
||||
location of the close parenthesis. This is safe since we
|
||||
guarantee that macro expansion does not alter the state of
|
||||
current_file/current_line (dnl, include, and sinclude are special
|
||||
cased in the input engine to ensure this fact). */
|
||||
const char *loc_open_file = current_file;
|
||||
int loc_open_line = current_line;
|
||||
const char *loc_close_file;
|
||||
int loc_close_line;
|
||||
|
||||
SYMBOL_PENDING_EXPANSIONS (sym)++;
|
||||
expansion_level++;
|
||||
if (nesting_limit > 0 && expansion_level > nesting_limit)
|
||||
m4_failure (0,
|
||||
_("recursion limit of %d exceeded, use -L<N> to change it"),
|
||||
nesting_limit);
|
||||
|
||||
macro_call_id++;
|
||||
my_call_id = macro_call_id;
|
||||
|
||||
traced = (debug_level & DEBUG_TRACE_ALL) || SYMBOL_TRACED (sym);
|
||||
|
||||
argv_base = obstack_object_size (&argv_stack);
|
||||
if (obstack_object_size (&argc_stack) > 0)
|
||||
{
|
||||
/* We cannot use argc_stack if this is a nested invocation, and an
|
||||
outer invocation has an unfinished argument being
|
||||
collected. */
|
||||
obstack_init (&arguments);
|
||||
use_argc_stack = false;
|
||||
}
|
||||
|
||||
if (traced && (debug_level & DEBUG_TRACE_CALL))
|
||||
trace_prepre (SYMBOL_NAME (sym), my_call_id);
|
||||
|
||||
collect_arguments (sym, &argv_stack,
|
||||
use_argc_stack ? &argc_stack : &arguments);
|
||||
|
||||
argc = ((obstack_object_size (&argv_stack) - argv_base)
|
||||
/ sizeof (token_data *));
|
||||
argv = (token_data **) ((uintptr_t) obstack_base (&argv_stack) + argv_base);
|
||||
|
||||
loc_close_file = current_file;
|
||||
loc_close_line = current_line;
|
||||
current_file = loc_open_file;
|
||||
current_line = loc_open_line;
|
||||
|
||||
if (traced)
|
||||
trace_pre (SYMBOL_NAME (sym), my_call_id, argc, argv);
|
||||
|
||||
expansion = push_string_init ();
|
||||
call_macro (sym, argc, argv, expansion);
|
||||
expanded = push_string_finish ();
|
||||
|
||||
if (traced)
|
||||
trace_post (SYMBOL_NAME (sym), my_call_id, argc, expanded);
|
||||
|
||||
current_file = loc_close_file;
|
||||
current_line = loc_close_line;
|
||||
|
||||
--expansion_level;
|
||||
--SYMBOL_PENDING_EXPANSIONS (sym);
|
||||
|
||||
if (SYMBOL_DELETED (sym))
|
||||
free_symbol (sym);
|
||||
|
||||
if (use_argc_stack)
|
||||
obstack_free (&argc_stack, argv[0]);
|
||||
else
|
||||
obstack_free (&arguments, NULL);
|
||||
obstack_blank_fast (&argv_stack, -argc * (ptrdiff_t) sizeof (token_data *));
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,204 @@
|
||||
/* GNU m4 -- A simple macro processor
|
||||
|
||||
Copyright (C) 1989-1993, 2004, 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/>.
|
||||
*/
|
||||
|
||||
/* Handling of path search of included files via the builtins "include"
|
||||
and "sinclude". */
|
||||
|
||||
#include "m4.h"
|
||||
|
||||
struct includes
|
||||
{
|
||||
struct includes *next; /* next directory to search */
|
||||
const char *dir; /* directory */
|
||||
int len;
|
||||
};
|
||||
|
||||
typedef struct includes includes;
|
||||
|
||||
static includes *dir_list; /* the list of path directories */
|
||||
static includes *dir_list_end; /* the end of same */
|
||||
static int dir_max_length; /* length of longest directory name */
|
||||
|
||||
|
||||
void
|
||||
include_init (void)
|
||||
{
|
||||
dir_list = NULL;
|
||||
dir_list_end = NULL;
|
||||
dir_max_length = 0;
|
||||
}
|
||||
|
||||
void
|
||||
include_env_init (void)
|
||||
{
|
||||
char *path;
|
||||
char *env_path;
|
||||
|
||||
if (no_gnu_extensions)
|
||||
return;
|
||||
|
||||
env_path = getenv ("M4PATH");
|
||||
if (env_path == NULL)
|
||||
return;
|
||||
|
||||
env_path = xstrdup (env_path);
|
||||
path = env_path;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
char *path_end = strchr (path, ':');
|
||||
if (path_end)
|
||||
*path_end = '\0';
|
||||
add_include_directory (path);
|
||||
if (!path_end)
|
||||
break;
|
||||
path = path_end + 1;
|
||||
}
|
||||
free (env_path);
|
||||
}
|
||||
|
||||
void
|
||||
add_include_directory (const char *dir)
|
||||
{
|
||||
includes *incl;
|
||||
|
||||
if (no_gnu_extensions)
|
||||
return;
|
||||
|
||||
if (*dir == '\0')
|
||||
dir = ".";
|
||||
|
||||
incl = (includes *) xmalloc (sizeof (struct includes));
|
||||
incl->next = NULL;
|
||||
incl->len = strlen (dir);
|
||||
incl->dir = xstrdup (dir);
|
||||
|
||||
if (incl->len > dir_max_length) /* remember len of longest directory */
|
||||
dir_max_length = incl->len;
|
||||
|
||||
if (dir_list_end == NULL)
|
||||
dir_list = incl;
|
||||
else
|
||||
dir_list_end->next = incl;
|
||||
dir_list_end = incl;
|
||||
|
||||
#ifdef DEBUG_INCL
|
||||
xfprintf (stderr, "add_include_directory (%s);\n", dir);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Attempt to open FILE; if it opens, verify that it is not a
|
||||
directory, and ensure it does not leak across execs. Use binary
|
||||
mode instead of text if BINARY is set. */
|
||||
static FILE *
|
||||
m4_fopen (const char *file, bool binary)
|
||||
{
|
||||
FILE *fp = fopen (file, binary ? "rbe" : "re");
|
||||
if (fp)
|
||||
{
|
||||
struct stat st;
|
||||
int fd = fileno (fp);
|
||||
if (fstat (fd, &st) == 0 && S_ISDIR (st.st_mode))
|
||||
{
|
||||
fclose (fp);
|
||||
errno = EISDIR;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
/* Search for FILE, first in `.', then according to -I options. If
|
||||
successful, return the open file (in BINARY mode if requested), and
|
||||
if RESULT is not NULL, set *RESULT to a malloc'd string that
|
||||
represents the file found with respect to the current working
|
||||
directory. */
|
||||
|
||||
FILE *
|
||||
m4_path_search (const char *file, bool binary, char **result)
|
||||
{
|
||||
FILE *fp;
|
||||
includes *incl;
|
||||
char *name; /* buffer for constructed name */
|
||||
int e;
|
||||
|
||||
if (result)
|
||||
*result = NULL;
|
||||
|
||||
/* Reject empty file. */
|
||||
if (!*file)
|
||||
{
|
||||
errno = ENOENT;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Look in current working directory first. */
|
||||
fp = m4_fopen (file, binary);
|
||||
if (fp != NULL)
|
||||
{
|
||||
if (result)
|
||||
*result = xstrdup (file);
|
||||
return fp;
|
||||
}
|
||||
|
||||
/* If file not found, and filename absolute, fail. */
|
||||
if (IS_ABSOLUTE_FILE_NAME (file) || no_gnu_extensions)
|
||||
return NULL;
|
||||
e = errno;
|
||||
|
||||
for (incl = dir_list; incl != NULL; incl = incl->next)
|
||||
{
|
||||
name = file_name_concat (incl->dir, file, NULL);
|
||||
|
||||
#ifdef DEBUG_INCL
|
||||
xfprintf (stderr, "m4_path_search (%s) -- trying %s\n", file, name);
|
||||
#endif
|
||||
|
||||
fp = m4_fopen (name, binary);
|
||||
if (fp != NULL)
|
||||
{
|
||||
if (debug_level & DEBUG_TRACE_PATH)
|
||||
DEBUG_MESSAGE2 ("path search for `%s' found `%s'", file, name);
|
||||
if (result)
|
||||
*result = name;
|
||||
else
|
||||
free (name);
|
||||
return fp;
|
||||
}
|
||||
free (name);
|
||||
}
|
||||
errno = e;
|
||||
return fp;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_INCL
|
||||
|
||||
static void MAYBE_UNUSED
|
||||
include_dump (void)
|
||||
{
|
||||
includes *incl;
|
||||
|
||||
xfprintf (stderr, "include_dump:\n");
|
||||
for (incl = dir_list; incl != NULL; incl = incl->next)
|
||||
xfprintf (stderr, "\t%s\n", incl->dir);
|
||||
}
|
||||
|
||||
#endif /* DEBUG_INCL */
|
||||
@@ -0,0 +1,460 @@
|
||||
/* GNU m4 -- A simple macro processor
|
||||
|
||||
Copyright (C) 1989-1994, 2003, 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/>.
|
||||
*/
|
||||
|
||||
/* This file handles all the low level work around the symbol table. The
|
||||
symbol table is a simple chained hash table. Each symbol is described
|
||||
by a struct symbol, which is placed in the hash table based upon the
|
||||
symbol name. Symbols that hash to the same entry in the table are
|
||||
kept on a list, sorted by hash. As a special case, to facilitate the
|
||||
"pushdef" and "popdef" builtins, a symbol can be several times in the
|
||||
symbol table, one for each definition. Since the name is the same,
|
||||
all the entries for the symbol will be on the same list, and will
|
||||
also, because the list is sorted, be adjacent. All the entries for a
|
||||
name are simply ordered on the list by age. The current definition
|
||||
will then always be the first found. */
|
||||
|
||||
#include "m4.h"
|
||||
#include <limits.h>
|
||||
|
||||
#ifdef DEBUG_SYM
|
||||
/* When evaluating hash table performance, this profiling code shows
|
||||
how many collisions were encountered. */
|
||||
|
||||
struct profile
|
||||
{
|
||||
int entry; /* Number of times lookup_symbol called with
|
||||
this mode. */
|
||||
int allocations; /* Number of times a symbol is malloc'd. */
|
||||
int hits; /* Number of times a symbol is found. */
|
||||
int checks; /* Number of times a hash is checked. */
|
||||
int comparisons; /* Number of times strcmp was called. */
|
||||
int misses; /* Number of times strcmp did not return 0. */
|
||||
long long bytes_hashed; /* Number of bytes hashed. */
|
||||
long long bytes_compared; /* Number of bytes compared. */
|
||||
};
|
||||
|
||||
static struct profile profiles[5];
|
||||
static symbol_lookup current_mode;
|
||||
|
||||
/* On exit, show a profile of symbol table performance. */
|
||||
static void
|
||||
show_profile (void)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < 5; i++)
|
||||
{
|
||||
xfprintf (stderr, "m4debug: lookup mode %d called %d times, %d hits:\n"
|
||||
"m4debug: symbols: %d allocs, %d checks, %lld bytes hashed\n"
|
||||
"m4debug: str: %d compares, %d misses, %lld bytes compared\n",
|
||||
i, profiles[i].entry, profiles[i].hits,
|
||||
profiles[i].allocations, profiles[i].checks,
|
||||
profiles[i].bytes_hashed, profiles[i].comparisons,
|
||||
profiles[i].misses, profiles[i].bytes_compared);
|
||||
}
|
||||
}
|
||||
|
||||
/* Like strcmp (S1, S2), but also track profiling statistics. */
|
||||
static int
|
||||
profile_strcmp (const char *s1, const char *s2)
|
||||
{
|
||||
int i = 1;
|
||||
int result;
|
||||
while (*s1 && *s1 == *s2)
|
||||
{
|
||||
s1++;
|
||||
s2++;
|
||||
i++;
|
||||
}
|
||||
result = (unsigned char) *s1 - (unsigned char) *s2;
|
||||
profiles[current_mode].comparisons++;
|
||||
if (result != 0)
|
||||
profiles[current_mode].misses++;
|
||||
profiles[current_mode].bytes_compared += i;
|
||||
return result;
|
||||
}
|
||||
|
||||
# define strcmp profile_strcmp
|
||||
#endif /* DEBUG_SYM */
|
||||
|
||||
|
||||
/*------------------------------------------------------------------.
|
||||
| Initialise the symbol table, by allocating the necessary storage, |
|
||||
| and zeroing all the entries. |
|
||||
`------------------------------------------------------------------*/
|
||||
|
||||
/* Pointer to symbol table. */
|
||||
static symbol **symtab;
|
||||
|
||||
void
|
||||
symtab_init (void)
|
||||
{
|
||||
size_t i;
|
||||
symbol **s;
|
||||
|
||||
s = symtab = (symbol **) xnmalloc (hash_table_size, sizeof (symbol *));
|
||||
|
||||
for (i = 0; i < hash_table_size; i++)
|
||||
s[i] = NULL;
|
||||
|
||||
#ifdef DEBUG_SYM
|
||||
{
|
||||
int e = atexit (show_profile);
|
||||
if (e != 0)
|
||||
M4ERROR ((warning_status, 0,
|
||||
"INTERNAL ERROR: unable to show symtab profile"));
|
||||
}
|
||||
#endif /* DEBUG_SYM */
|
||||
}
|
||||
|
||||
/*--------------------------------------------------.
|
||||
| Return a hashvalue for a string, from GNU-emacs. |
|
||||
`--------------------------------------------------*/
|
||||
|
||||
static size_t ATTRIBUTE_PURE
|
||||
hash (const char *s)
|
||||
{
|
||||
register size_t val = 0;
|
||||
|
||||
register const char *ptr = s;
|
||||
register char ch;
|
||||
|
||||
while ((ch = *ptr++) != '\0')
|
||||
val = (val << 7) + (val >> (sizeof (val) * CHAR_BIT - 7)) + ch;
|
||||
return val;
|
||||
}
|
||||
|
||||
/*--------------------------------------------.
|
||||
| Free all storage associated with a symbol. |
|
||||
`--------------------------------------------*/
|
||||
|
||||
void
|
||||
free_symbol (symbol *sym)
|
||||
{
|
||||
if (SYMBOL_PENDING_EXPANSIONS (sym) > 0)
|
||||
{
|
||||
SYMBOL_DELETED (sym) = true;
|
||||
if (SYMBOL_STACK (sym))
|
||||
{
|
||||
SYMBOL_NAME (sym) = xmemdup0 (SYMBOL_NAME (sym),
|
||||
SYMBOL_NAME_LEN (sym));
|
||||
SYMBOL_STACK (sym) = NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SYMBOL_STACK (sym) == NULL)
|
||||
free (SYMBOL_NAME (sym));
|
||||
if (SYMBOL_TYPE (sym) == TOKEN_TEXT)
|
||||
free (SYMBOL_TEXT (sym));
|
||||
free (sym);
|
||||
}
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------.
|
||||
| Search in, and manipulation of the symbol table, are all done by |
|
||||
| lookup_symbol (). It basically hashes NAME to a list in the |
|
||||
| symbol table, and searches this list for the first occurrence of a |
|
||||
| symbol with the name. |
|
||||
| |
|
||||
| The MODE parameter determines what lookup_symbol () will do. It |
|
||||
| can either just do a lookup, do a lookup and insert if not |
|
||||
| present, do an insertion even if the name is already in the list, |
|
||||
| delete the first occurrence of the name on the list, or delete all |
|
||||
| occurrences of the name on the list. |
|
||||
`-------------------------------------------------------------------*/
|
||||
|
||||
symbol *
|
||||
lookup_symbol (const char *name, int len, symbol_lookup mode)
|
||||
{
|
||||
size_t h;
|
||||
int cmp = 1;
|
||||
symbol *sym, *prev;
|
||||
symbol **spp;
|
||||
|
||||
#if DEBUG_SYM
|
||||
current_mode = mode;
|
||||
profiles[mode].entry++;
|
||||
profiles[mode].bytes_hashed += len;
|
||||
#endif /* DEBUG_SYM */
|
||||
|
||||
h = hash (name);
|
||||
sym = symtab[h % hash_table_size];
|
||||
|
||||
for (prev = NULL; sym != NULL; prev = sym, sym = sym->next)
|
||||
{
|
||||
#ifdef DEBUG_SYM
|
||||
profiles[mode].checks++;
|
||||
#endif
|
||||
cmp = (h > sym->hash) - (h < sym->hash);
|
||||
if (cmp == 0)
|
||||
cmp = strcmp (SYMBOL_NAME (sym), name);
|
||||
if (cmp >= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
/* If just searching, return status of search. */
|
||||
|
||||
#ifdef DEBUG_SYM
|
||||
if (cmp == 0)
|
||||
profiles[mode].hits++;
|
||||
#endif
|
||||
if (mode == SYMBOL_LOOKUP)
|
||||
return cmp == 0 ? sym : NULL;
|
||||
|
||||
/* Symbol not found. */
|
||||
|
||||
spp = (prev != NULL) ? &prev->next : &symtab[h % hash_table_size];
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
|
||||
case SYMBOL_INSERT:
|
||||
|
||||
/* If the name was found in the table, check whether it is still in
|
||||
use by a pending expansion. If so, replace the table element with
|
||||
a new one; if not, just return the symbol. If not found, just
|
||||
insert the name, and return the new symbol. */
|
||||
|
||||
if (cmp == 0 && sym != NULL)
|
||||
{
|
||||
if (SYMBOL_PENDING_EXPANSIONS (sym) > 0)
|
||||
{
|
||||
symbol *old = sym;
|
||||
SYMBOL_DELETED (old) = true;
|
||||
|
||||
#ifdef DEBUG_SYM
|
||||
profiles[mode].allocations++;
|
||||
#endif
|
||||
sym = (symbol *) xmalloc (sizeof (symbol));
|
||||
SYMBOL_TYPE (sym) = TOKEN_VOID;
|
||||
SYMBOL_TRACED (sym) = SYMBOL_TRACED (old);
|
||||
sym->hash = h;
|
||||
SYMBOL_NAME (sym) = SYMBOL_NAME (old);
|
||||
SYMBOL_NAME_LEN (sym) = SYMBOL_NAME_LEN (old);
|
||||
SYMBOL_MACRO_ARGS (sym) = false;
|
||||
SYMBOL_BLIND_NO_ARGS (sym) = false;
|
||||
SYMBOL_DELETED (sym) = false;
|
||||
SYMBOL_PENDING_EXPANSIONS (sym) = 0;
|
||||
|
||||
SYMBOL_STACK (sym) = SYMBOL_STACK (old);
|
||||
SYMBOL_STACK (old) = sym;
|
||||
sym->next = old->next;
|
||||
old->next = NULL;
|
||||
*spp = sym;
|
||||
}
|
||||
return sym;
|
||||
}
|
||||
FALLTHROUGH;
|
||||
|
||||
case SYMBOL_PUSHDEF:
|
||||
|
||||
/* Insert a name in the symbol table. If there is already a symbol
|
||||
with the name, insert this in front of it. */
|
||||
|
||||
#ifdef DEBUG_SYM
|
||||
profiles[mode].allocations++;
|
||||
#endif
|
||||
sym = (symbol *) xmalloc (sizeof (symbol));
|
||||
SYMBOL_TYPE (sym) = TOKEN_VOID;
|
||||
SYMBOL_TRACED (sym) = false;
|
||||
sym->hash = h;
|
||||
SYMBOL_MACRO_ARGS (sym) = false;
|
||||
SYMBOL_BLIND_NO_ARGS (sym) = false;
|
||||
SYMBOL_DELETED (sym) = false;
|
||||
SYMBOL_PENDING_EXPANSIONS (sym) = 0;
|
||||
|
||||
SYMBOL_STACK (sym) = NULL;
|
||||
sym->next = *spp;
|
||||
*spp = sym;
|
||||
|
||||
if (mode == SYMBOL_PUSHDEF && cmp == 0)
|
||||
{
|
||||
SYMBOL_STACK (sym) = sym->next;
|
||||
sym->next = SYMBOL_STACK (sym)->next;
|
||||
SYMBOL_STACK (sym)->next = NULL;
|
||||
SYMBOL_TRACED (sym) = SYMBOL_TRACED (SYMBOL_STACK (sym));
|
||||
SYMBOL_NAME (sym) = SYMBOL_NAME (SYMBOL_STACK (sym));
|
||||
SYMBOL_NAME_LEN (sym) = SYMBOL_NAME_LEN (SYMBOL_STACK (sym));
|
||||
}
|
||||
else
|
||||
{
|
||||
SYMBOL_NAME (sym) = xmemdup0 (name, len);
|
||||
SYMBOL_NAME_LEN (sym) = len;
|
||||
}
|
||||
return sym;
|
||||
|
||||
case SYMBOL_DELETE:
|
||||
case SYMBOL_POPDEF:
|
||||
|
||||
/* Delete occurrences of symbols with NAME. SYMBOL_DELETE kills
|
||||
all definitions, SYMBOL_POPDEF kills only the first.
|
||||
However, if the last instance of a symbol is marked for
|
||||
tracing, reinsert a placeholder in the table. And if the
|
||||
definition is still in use, let the caller free the memory
|
||||
after it is done with the symbol. */
|
||||
|
||||
if (cmp != 0)
|
||||
return NULL;
|
||||
if (sym == NULL)
|
||||
return NULL;
|
||||
{
|
||||
bool traced = false;
|
||||
symbol *next;
|
||||
if (SYMBOL_STACK (sym) != NULL && mode == SYMBOL_POPDEF)
|
||||
{
|
||||
SYMBOL_TRACED (SYMBOL_STACK (sym)) = SYMBOL_TRACED (sym);
|
||||
SYMBOL_STACK (sym)->next = sym->next;
|
||||
*spp = SYMBOL_STACK (sym);
|
||||
}
|
||||
else
|
||||
{
|
||||
traced = SYMBOL_TRACED (sym);
|
||||
*spp = sym->next;
|
||||
}
|
||||
do
|
||||
{
|
||||
next = SYMBOL_STACK (sym);
|
||||
free_symbol (sym);
|
||||
sym = next;
|
||||
}
|
||||
while (next != NULL && mode == SYMBOL_DELETE);
|
||||
if (traced)
|
||||
{
|
||||
#ifdef DEBUG_SYM
|
||||
profiles[mode].allocations++;
|
||||
#endif
|
||||
sym = (symbol *) xmalloc (sizeof (symbol));
|
||||
SYMBOL_TYPE (sym) = TOKEN_VOID;
|
||||
SYMBOL_TRACED (sym) = true;
|
||||
sym->hash = h;
|
||||
SYMBOL_NAME (sym) = xmemdup0 (name, len);
|
||||
SYMBOL_NAME_LEN (sym) = len;
|
||||
SYMBOL_MACRO_ARGS (sym) = false;
|
||||
SYMBOL_BLIND_NO_ARGS (sym) = false;
|
||||
SYMBOL_DELETED (sym) = false;
|
||||
SYMBOL_PENDING_EXPANSIONS (sym) = 0;
|
||||
|
||||
SYMBOL_STACK (sym) = NULL;
|
||||
sym->next = *spp;
|
||||
*spp = sym;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
|
||||
case SYMBOL_LOOKUP:
|
||||
default:
|
||||
M4ERROR ((warning_status, 0,
|
||||
"INTERNAL ERROR: invalid mode to symbol_lookup ()"));
|
||||
abort ();
|
||||
}
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------.
|
||||
| The following function is used for the cases where we want to do |
|
||||
| something to each and every symbol in the table. The function |
|
||||
| hack_all_symbols () traverses the symbol table, and calls a |
|
||||
| specified function FUNC for each symbol in the table. FUNC is |
|
||||
| called with a pointer to the symbol, and the DATA argument. |
|
||||
| |
|
||||
| FUNC may safely call lookup_symbol with mode SYMBOL_POPDEF or |
|
||||
| SYMBOL_LOOKUP, but any other mode can break the iteration. |
|
||||
`-----------------------------------------------------------------*/
|
||||
|
||||
void
|
||||
hack_all_symbols (hack_symbol *func, void *data)
|
||||
{
|
||||
size_t h;
|
||||
symbol *sym;
|
||||
symbol *next;
|
||||
|
||||
for (h = 0; h < hash_table_size; h++)
|
||||
{
|
||||
/* We allow func to call SYMBOL_POPDEF, which can invalidate
|
||||
sym, so we must grab the next element to traverse before
|
||||
calling func. */
|
||||
for (sym = symtab[h]; sym != NULL; sym = next)
|
||||
{
|
||||
next = sym->next;
|
||||
func (sym, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_SYM
|
||||
|
||||
static void symtab_print_list (int i);
|
||||
|
||||
static void MAYBE_UNUSED
|
||||
symtab_debug (void)
|
||||
{
|
||||
token_data td;
|
||||
const char *text;
|
||||
symbol *s;
|
||||
int delete;
|
||||
static int i;
|
||||
int len;
|
||||
|
||||
while (next_token (&td, NULL) == TOKEN_WORD)
|
||||
{
|
||||
text = TOKEN_DATA_TEXT (&td);
|
||||
len = TOKEN_DATA_LEN (&td);
|
||||
if (*text == '_')
|
||||
{
|
||||
delete = 1;
|
||||
text++;
|
||||
len--;
|
||||
}
|
||||
else
|
||||
delete = 0;
|
||||
|
||||
s = lookup_symbol (text, len, SYMBOL_LOOKUP);
|
||||
|
||||
if (s == NULL)
|
||||
xprintf ("Name `%s' is unknown\n", text);
|
||||
|
||||
lookup_symbol (text, len, delete ? SYMBOL_DELETE : SYMBOL_INSERT);
|
||||
}
|
||||
symtab_print_list (i++);
|
||||
}
|
||||
|
||||
static void
|
||||
symtab_print_list (int i)
|
||||
{
|
||||
symbol *sym;
|
||||
symbol *bucket;
|
||||
size_t h;
|
||||
|
||||
xprintf ("Symbol dump #%d:\n", i);
|
||||
for (h = 0; h < hash_table_size; h++)
|
||||
for (bucket = symtab[h]; bucket != NULL; bucket = bucket->next)
|
||||
for (sym = bucket; sym; sym = sym->stack)
|
||||
xprintf ("\tname %s, len %i, hash %lu, bucket %lu, addr %p, "
|
||||
"stack %p, next %p, flags%s%s, pending %d\n",
|
||||
SYMBOL_NAME (sym), SYMBOL_NAME_LEN (sym),
|
||||
(unsigned long int) sym->hash,
|
||||
(unsigned long int) h, sym, SYMBOL_STACK (sym),
|
||||
sym->next,
|
||||
SYMBOL_TRACED (sym) ? " traced" : "",
|
||||
SYMBOL_DELETED (sym) ? " deleted" : "",
|
||||
SYMBOL_PENDING_EXPANSIONS (sym));
|
||||
}
|
||||
|
||||
#endif /* DEBUG_SYM */
|
||||
Reference in New Issue
Block a user