summaryrefslogtreecommitdiff
path: root/src/macro.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/macro.c')
-rw-r--r--src/macro.c391
1 files changed, 391 insertions, 0 deletions
diff --git a/src/macro.c b/src/macro.c
new file mode 100644
index 0000000..0e76243
--- /dev/null
+++ b/src/macro.c
@@ -0,0 +1,391 @@
+/* GNU m4 -- A simple macro processor
+
+ Copyright (C) 1989-1994, 2006-2007, 2009-2013 Free Software
+ Foundation, Inc.
+
+ This file is part of GNU M4.
+
+ GNU M4 is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ GNU M4 is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* 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 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), 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)
+{
+ token_type t;
+ token_data td;
+ char *text;
+ int paren_level;
+ const char *file = current_file;
+ int line = current_line;
+
+ TOKEN_DATA_TYPE (argp) = TOKEN_VOID;
+
+ /* Skip leading white space. */
+ do
+ {
+ t = next_token (&td, NULL);
+ }
+ while (t == TOKEN_SIMPLE && isspace (to_uchar (*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');
+ text = (char *) obstack_finish (obs);
+
+ if (TOKEN_DATA_TYPE (argp) == TOKEN_VOID)
+ {
+ TOKEN_DATA_TYPE (argp) = TOKEN_TEXT;
+ TOKEN_DATA_TEXT (argp) = text;
+ }
+ return t == TOKEN_COMMA;
+ }
+ /* fallthru */
+ 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. */
+ M4ERROR_AT_LINE ((EXIT_FAILURE, 0, file, line,
+ "ERROR: end of file in argument list"));
+ break;
+
+ case TOKEN_WORD:
+ case TOKEN_STRING:
+ expand_token (obs, t, &td, line);
+ break;
+
+ case TOKEN_MACDEF:
+ if (obstack_object_size (obs) == 0)
+ {
+ TOKEN_DATA_TYPE (argp) = TOKEN_FUNC;
+ TOKEN_DATA_FUNC (argp) = TOKEN_DATA_FUNC (&td);
+ }
+ 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_args = SYMBOL_MACRO_ARGS (sym);
+
+ TOKEN_DATA_TYPE (&td) = TOKEN_TEXT;
+ TOKEN_DATA_TEXT (&td) = SYMBOL_NAME (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);
+
+ if (!groks_macro_args && TOKEN_DATA_TYPE (&td) == TOKEN_FUNC)
+ {
+ TOKEN_DATA_TYPE (&td) = TOKEN_TEXT;
+ TOKEN_DATA_TEXT (&td) = (char *) "";
+ }
+ 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)
+ M4ERROR ((EXIT_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 **) ((char *) 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 (&argv_stack, -argc * sizeof (token_data *));
+}