406 lines
13 KiB
C
406 lines
13 KiB
C
/* 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 *));
|
|
}
|