fix: noconfirm auto-selects first AUR match

This commit is contained in:
2026-05-08 11:01:02 +01:00
parent d39cdc3fd9
commit 153cca6132
8056 changed files with 1983098 additions and 779 deletions
@@ -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
+436
View File
@@ -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 ();
}
+615
View File
@@ -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;
}
+396
View File
@@ -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);
}
}
+396
View File
@@ -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
+743
View File
@@ -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&eacute;". */
#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);
}
+519
View File
@@ -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)
+405
View File
@@ -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
+204
View File
@@ -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 */
+460
View File
@@ -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 */