diff options
author | axc <axc@yandex-team.ru> | 2022-02-10 16:47:35 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:47:35 +0300 |
commit | 1f5217043ad70f25dc35e75b3bd261a1e23d045e (patch) | |
tree | 11bf68c1fa5272d3d3446cbd5a0ff96ed9d75788 /contrib/tools/bison/m4/src/builtin.c | |
parent | 69505a07cbb096113e85aa02e7d136cac4aa826c (diff) | |
download | ydb-1f5217043ad70f25dc35e75b3bd261a1e23d045e.tar.gz |
Restoring authorship annotation for <axc@yandex-team.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/tools/bison/m4/src/builtin.c')
-rw-r--r-- | contrib/tools/bison/m4/src/builtin.c | 4514 |
1 files changed, 2257 insertions, 2257 deletions
diff --git a/contrib/tools/bison/m4/src/builtin.c b/contrib/tools/bison/m4/src/builtin.c index 01ede38017..3b867da712 100644 --- a/contrib/tools/bison/m4/src/builtin.c +++ b/contrib/tools/bison/m4/src/builtin.c @@ -1,2258 +1,2258 @@ -/* GNU m4 -- A simple macro processor - - Copyright (C) 1989-1994, 2000, 2004, 2006-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/>. -*/ - -/* Code for all builtin macros, initialization of symbol table, and - expansion of user defined macros. */ - -#include "m4.h" - -#include "execute.h" -#include "memchr2.h" -#include "progname.h" +/* GNU m4 -- A simple macro processor + + Copyright (C) 1989-1994, 2000, 2004, 2006-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/>. +*/ + +/* Code for all builtin macros, initialization of symbol table, and + expansion of user defined macros. */ + +#include "m4.h" + +#include "execute.h" +#include "memchr2.h" +#include "progname.h" #include <contrib/tools/bison/gnulib/src/regex.h> -#include "spawn-pipe.h" -#include "wait-process.h" - -#define ARG(i) (argc > (i) ? TOKEN_DATA_TEXT (argv[i]) : "") - -/* Initialization of builtin and predefined macros. The table - "builtin_tab" is both used for initialization, and by the "builtin" - builtin. */ - -#define DECLARE(name) \ - static void name (struct obstack *, int, token_data **) - -DECLARE (m4___file__); -DECLARE (m4___line__); -DECLARE (m4___program__); -DECLARE (m4_builtin); -DECLARE (m4_changecom); -DECLARE (m4_changequote); -#ifdef ENABLE_CHANGEWORD -DECLARE (m4_changeword); -#endif -DECLARE (m4_debugmode); -DECLARE (m4_debugfile); -DECLARE (m4_decr); -DECLARE (m4_define); -DECLARE (m4_defn); -DECLARE (m4_divert); -DECLARE (m4_divnum); -DECLARE (m4_dnl); -DECLARE (m4_dumpdef); -DECLARE (m4_errprint); -DECLARE (m4_esyscmd); -DECLARE (m4_eval); -DECLARE (m4_format); -DECLARE (m4_ifdef); -DECLARE (m4_ifelse); -DECLARE (m4_include); -DECLARE (m4_incr); -DECLARE (m4_index); -DECLARE (m4_indir); -DECLARE (m4_len); -DECLARE (m4_m4exit); -DECLARE (m4_m4wrap); -DECLARE (m4_maketemp); -DECLARE (m4_mkstemp); -DECLARE (m4_patsubst); -DECLARE (m4_popdef); -DECLARE (m4_pushdef); -DECLARE (m4_regexp); -DECLARE (m4_shift); -DECLARE (m4_sinclude); -DECLARE (m4_substr); -DECLARE (m4_syscmd); -DECLARE (m4_sysval); -DECLARE (m4_traceoff); -DECLARE (m4_traceon); -DECLARE (m4_translit); -DECLARE (m4_undefine); -DECLARE (m4_undivert); - -#undef DECLARE - -static builtin const builtin_tab[] = -{ - - /* name GNUext macros blind function */ - - { "__file__", true, false, false, m4___file__ }, - { "__line__", true, false, false, m4___line__ }, - { "__program__", true, false, false, m4___program__ }, - { "builtin", true, true, true, m4_builtin }, - { "changecom", false, false, false, m4_changecom }, - { "changequote", false, false, false, m4_changequote }, -#ifdef ENABLE_CHANGEWORD - { "changeword", true, false, true, m4_changeword }, -#endif - { "debugmode", true, false, false, m4_debugmode }, - { "debugfile", true, false, false, m4_debugfile }, - { "decr", false, false, true, m4_decr }, - { "define", false, true, true, m4_define }, - { "defn", false, false, true, m4_defn }, - { "divert", false, false, false, m4_divert }, - { "divnum", false, false, false, m4_divnum }, - { "dnl", false, false, false, m4_dnl }, - { "dumpdef", false, false, false, m4_dumpdef }, - { "errprint", false, false, true, m4_errprint }, - { "esyscmd", true, false, true, m4_esyscmd }, - { "eval", false, false, true, m4_eval }, - { "format", true, false, true, m4_format }, - { "ifdef", false, false, true, m4_ifdef }, - { "ifelse", false, false, true, m4_ifelse }, - { "include", false, false, true, m4_include }, - { "incr", false, false, true, m4_incr }, - { "index", false, false, true, m4_index }, - { "indir", true, true, true, m4_indir }, - { "len", false, false, true, m4_len }, - { "m4exit", false, false, false, m4_m4exit }, - { "m4wrap", false, false, true, m4_m4wrap }, - { "maketemp", false, false, true, m4_maketemp }, - { "mkstemp", false, false, true, m4_mkstemp }, - { "patsubst", true, false, true, m4_patsubst }, - { "popdef", false, false, true, m4_popdef }, - { "pushdef", false, true, true, m4_pushdef }, - { "regexp", true, false, true, m4_regexp }, - { "shift", false, false, true, m4_shift }, - { "sinclude", false, false, true, m4_sinclude }, - { "substr", false, false, true, m4_substr }, - { "syscmd", false, false, true, m4_syscmd }, - { "sysval", false, false, false, m4_sysval }, - { "traceoff", false, false, false, m4_traceoff }, - { "traceon", false, false, false, m4_traceon }, - { "translit", false, false, true, m4_translit }, - { "undefine", false, false, true, m4_undefine }, - { "undivert", false, false, false, m4_undivert }, - - { 0, false, false, false, 0 }, - - /* placeholder is intentionally stuck after the table end delimiter, - so that we can easily find it, while not treating it as a real - builtin. */ - { "placeholder", true, false, false, m4_placeholder }, -}; - -static predefined const predefined_tab[] = -{ -#if UNIX - { "unix", "__unix__", "" }, -#endif -#if W32_NATIVE - { "windows", "__windows__", "" }, -#endif -#if OS2 - { "os2", "__os2__", "" }, -#endif -#if !UNIX && !W32_NATIVE && !OS2 -# warning Platform macro not provided -#endif - { NULL, "__gnu__", "" }, - - { NULL, NULL, NULL }, -}; - -/*----------------------------------------. -| Find the builtin, which lives on ADDR. | -`----------------------------------------*/ - -const builtin * M4_GNUC_PURE -find_builtin_by_addr (builtin_func *func) -{ - const builtin *bp; - - for (bp = &builtin_tab[0]; bp->name != NULL; bp++) - if (bp->func == func) - return bp; - if (func == m4_placeholder) - return bp + 1; - return NULL; -} - -/*----------------------------------------------------------. -| Find the builtin, which has NAME. On failure, return the | -| placeholder builtin. | -`----------------------------------------------------------*/ - -const builtin * M4_GNUC_PURE -find_builtin_by_name (const char *name) -{ - const builtin *bp; - - for (bp = &builtin_tab[0]; bp->name != NULL; bp++) - if (STREQ (bp->name, name)) - return bp; - return bp + 1; -} - -/*----------------------------------------------------------------. -| Install a builtin macro with name NAME, bound to the C function | -| given in BP. MODE is SYMBOL_INSERT or SYMBOL_PUSHDEF. | -`----------------------------------------------------------------*/ - -void -define_builtin (const char *name, const builtin *bp, symbol_lookup mode) -{ - symbol *sym; - - sym = lookup_symbol (name, mode); - SYMBOL_TYPE (sym) = TOKEN_FUNC; - SYMBOL_MACRO_ARGS (sym) = bp->groks_macro_args; - SYMBOL_BLIND_NO_ARGS (sym) = bp->blind_if_no_args; - SYMBOL_FUNC (sym) = bp->func; -} - -/* Storage for the compiled regular expression of - --warn-macro-sequence. */ -static struct re_pattern_buffer macro_sequence_buf; - -/* Storage for the matches of --warn-macro-sequence. */ -static struct re_registers macro_sequence_regs; - -/* True if --warn-macro-sequence is in effect. */ -static bool macro_sequence_inuse; - -/*----------------------------------------. -| Clean up regular expression variables. | -`----------------------------------------*/ - -static void -free_pattern_buffer (struct re_pattern_buffer *buf, struct re_registers *regs) -{ - regfree (buf); - free (regs->start); - free (regs->end); -} - -/*-----------------------------------------------------------------. -| Set the regular expression of --warn-macro-sequence that will be | -| checked during define and pushdef. Exit on failure. | -`-----------------------------------------------------------------*/ -void -set_macro_sequence (const char *regexp) -{ - const char *msg; - - if (! regexp) - regexp = DEFAULT_MACRO_SEQUENCE; - else if (regexp[0] == '\0') - { - macro_sequence_inuse = false; - return; - } - - msg = re_compile_pattern (regexp, strlen (regexp), ¯o_sequence_buf); - if (msg != NULL) - { - M4ERROR ((EXIT_FAILURE, 0, - "--warn-macro-sequence: bad regular expression `%s': %s", - regexp, msg)); - } - re_set_registers (¯o_sequence_buf, ¯o_sequence_regs, - macro_sequence_regs.num_regs, - macro_sequence_regs.start, macro_sequence_regs.end); - macro_sequence_inuse = true; -} - -/*-----------------------------------------------------------. -| Free dynamic memory utilized by the macro sequence regular | -| expression during the define builtin. | -`-----------------------------------------------------------*/ -void -free_macro_sequence (void) -{ - free_pattern_buffer (¯o_sequence_buf, ¯o_sequence_regs); -} - -/*-----------------------------------------------------------------. -| Define a predefined or user-defined macro, with name NAME, and | -| expansion TEXT. MODE destinguishes between the "define" and the | -| "pushdef" case. It is also used from main. | -`-----------------------------------------------------------------*/ - -void -define_user_macro (const char *name, const char *text, symbol_lookup mode) -{ - symbol *s; - char *defn = xstrdup (text ? text : ""); - - s = lookup_symbol (name, mode); - if (SYMBOL_TYPE (s) == TOKEN_TEXT) - free (SYMBOL_TEXT (s)); - - SYMBOL_TYPE (s) = TOKEN_TEXT; - SYMBOL_TEXT (s) = defn; - - /* Implement --warn-macro-sequence. */ - if (macro_sequence_inuse && text) - { - regoff_t offset = 0; - size_t len = strlen (defn); - - while ((offset = re_search (¯o_sequence_buf, defn, len, offset, - len - offset, ¯o_sequence_regs)) >= 0) - { - /* Skip empty matches. */ - if (macro_sequence_regs.start[0] == macro_sequence_regs.end[0]) - offset++; - else - { - char tmp; - offset = macro_sequence_regs.end[0]; - tmp = defn[offset]; - defn[offset] = '\0'; - M4ERROR ((warning_status, 0, - "Warning: definition of `%s' contains sequence `%s'", - name, defn + macro_sequence_regs.start[0])); - defn[offset] = tmp; - } - } - if (offset == -2) - M4ERROR ((warning_status, 0, - "error checking --warn-macro-sequence for macro `%s'", - name)); - } -} - -/*-----------------------------------------------. -| Initialize all builtin and predefined macros. | -`-----------------------------------------------*/ - -void -builtin_init (void) -{ - const builtin *bp; - const predefined *pp; - char *string; - - for (bp = &builtin_tab[0]; bp->name != NULL; bp++) - if (!no_gnu_extensions || !bp->gnu_extension) - { - if (prefix_all_builtins) - { - string = (char *) xmalloc (strlen (bp->name) + 4); - strcpy (string, "m4_"); - strcat (string, bp->name); - define_builtin (string, bp, SYMBOL_INSERT); - free (string); - } - else - define_builtin (bp->name, bp, SYMBOL_INSERT); - } - - for (pp = &predefined_tab[0]; pp->func != NULL; pp++) - if (no_gnu_extensions) - { - if (pp->unix_name != NULL) - define_user_macro (pp->unix_name, pp->func, SYMBOL_INSERT); - } - else - { - if (pp->gnu_name != NULL) - define_user_macro (pp->gnu_name, pp->func, SYMBOL_INSERT); - } -} - -/*-------------------------------------------------------------------. -| Give friendly warnings if a builtin macro is passed an | -| inappropriate number of arguments. NAME is the macro name for | -| messages, ARGC is actual number of arguments, MIN is the minimum | -| number of acceptable arguments, negative if not applicable, MAX is | -| the maximum number, negative if not applicable. | -`-------------------------------------------------------------------*/ - -static bool -bad_argc (token_data *name, int argc, int min, int max) -{ - bool isbad = false; - - if (min > 0 && argc < min) - { - if (!suppress_warnings) - M4ERROR ((warning_status, 0, - "Warning: too few arguments to builtin `%s'", - TOKEN_DATA_TEXT (name))); - isbad = true; - } - else if (max > 0 && argc > max && !suppress_warnings) - M4ERROR ((warning_status, 0, - "Warning: excess arguments to builtin `%s' ignored", - TOKEN_DATA_TEXT (name))); - - return isbad; -} - -/*-----------------------------------------------------------------. -| The function numeric_arg () converts ARG to an int pointed to by | -| VALUEP. If the conversion fails, print error message for macro | -| MACRO. Return true iff conversion succeeds. | -`-----------------------------------------------------------------*/ - -static bool -numeric_arg (token_data *macro, const char *arg, int *valuep) -{ - char *endp; - - if (*arg == '\0') - { - *valuep = 0; - M4ERROR ((warning_status, 0, - "empty string treated as 0 in builtin `%s'", - TOKEN_DATA_TEXT (macro))); - } - else - { - errno = 0; - *valuep = strtol (arg, &endp, 10); - if (*endp != '\0') - { - M4ERROR ((warning_status, 0, - "non-numeric argument to builtin `%s'", - TOKEN_DATA_TEXT (macro))); - return false; - } - if (isspace (to_uchar (*arg))) - M4ERROR ((warning_status, 0, - "leading whitespace ignored in builtin `%s'", - TOKEN_DATA_TEXT (macro))); - else if (errno == ERANGE) - M4ERROR ((warning_status, 0, - "numeric overflow detected in builtin `%s'", - TOKEN_DATA_TEXT (macro))); - } - return true; -} - -/*------------------------------------------------------. -| The function ntoa () converts VALUE to a signed ASCII | -| representation in radix RADIX. | -`------------------------------------------------------*/ - -/* Digits for number to ASCII conversions. */ -static char const digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; - -const char * -ntoa (int32_t value, int radix) -{ - bool negative; - uint32_t uvalue; - static char str[256]; - char *s = &str[sizeof str]; - - *--s = '\0'; - - if (value < 0) - { - negative = true; - uvalue = -(uint32_t) value; - } - else - { - negative = false; - uvalue = (uint32_t) value; - } - - do - { - *--s = digits[uvalue % radix]; - uvalue /= radix; - } - while (uvalue > 0); - - if (negative) - *--s = '-'; - return s; -} - -/*---------------------------------------------------------------. -| Format an int VAL, and stuff it into an obstack OBS. Used for | -| macros expanding to numbers. | -`---------------------------------------------------------------*/ - -static void -shipout_int (struct obstack *obs, int val) -{ - const char *s; - - s = ntoa ((int32_t) val, 10); - obstack_grow (obs, s, strlen (s)); -} - -/*-------------------------------------------------------------------. -| Print ARGC arguments from the table ARGV to obstack OBS, separated | -| by SEP, and quoted by the current quotes if QUOTED is true. | -`-------------------------------------------------------------------*/ - -static void -dump_args (struct obstack *obs, int argc, token_data **argv, - const char *sep, bool quoted) -{ - int i; - size_t len = strlen (sep); - - for (i = 1; i < argc; i++) - { - if (i > 1) - obstack_grow (obs, sep, len); - if (quoted) - obstack_grow (obs, lquote.string, lquote.length); - obstack_grow (obs, TOKEN_DATA_TEXT (argv[i]), - strlen (TOKEN_DATA_TEXT (argv[i]))); - if (quoted) - obstack_grow (obs, rquote.string, rquote.length); - } -} - -/* The rest of this file is code for builtins and expansion of user - defined macros. All the functions for builtins have a prototype as: - - void m4_MACRONAME (struct obstack *obs, int argc, char *argv[]); - - The function are expected to leave their expansion on the obstack OBS, - as an unfinished object. ARGV is a table of ARGC pointers to the - individual arguments to the macro. Please note that in general - argv[argc] != NULL. */ - -/* The first section are macros for definining, undefining, examining, - changing, ... other macros. */ - -/*-------------------------------------------------------------------. -| The function define_macro is common for the builtins "define", | -| "undefine", "pushdef" and "popdef". ARGC and ARGV is as for the | -| caller, and MODE argument determines how the macro name is entered | -| into the symbol table. | -`-------------------------------------------------------------------*/ - -static void -define_macro (int argc, token_data **argv, symbol_lookup mode) -{ - const builtin *bp; - - if (bad_argc (argv[0], argc, 2, 3)) - return; - - if (TOKEN_DATA_TYPE (argv[1]) != TOKEN_TEXT) - { - M4ERROR ((warning_status, 0, - "Warning: %s: invalid macro name ignored", ARG (0))); - return; - } - - if (argc == 2) - { - define_user_macro (ARG (1), "", mode); - return; - } - - switch (TOKEN_DATA_TYPE (argv[2])) - { - case TOKEN_TEXT: - define_user_macro (ARG (1), ARG (2), mode); - break; - - case TOKEN_FUNC: - bp = find_builtin_by_addr (TOKEN_DATA_FUNC (argv[2])); - if (bp == NULL) - return; - else - define_builtin (ARG (1), bp, mode); - break; - - case TOKEN_VOID: - default: - M4ERROR ((warning_status, 0, - "INTERNAL ERROR: bad token data type in define_macro ()")); - abort (); - } -} - -static void -m4_define (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) -{ - define_macro (argc, argv, SYMBOL_INSERT); -} - -static void -m4_undefine (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) -{ - int i; - if (bad_argc (argv[0], argc, 2, -1)) - return; - for (i = 1; i < argc; i++) - lookup_symbol (ARG (i), SYMBOL_DELETE); -} - -static void -m4_pushdef (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) -{ - define_macro (argc, argv, SYMBOL_PUSHDEF); -} - -static void -m4_popdef (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) -{ - int i; - if (bad_argc (argv[0], argc, 2, -1)) - return; - for (i = 1; i < argc; i++) - lookup_symbol (ARG (i), SYMBOL_POPDEF); -} - -/*---------------------. -| Conditionals of m4. | -`---------------------*/ - -static void -m4_ifdef (struct obstack *obs, int argc, token_data **argv) -{ - symbol *s; - const char *result; - - if (bad_argc (argv[0], argc, 3, 4)) - return; - s = lookup_symbol (ARG (1), SYMBOL_LOOKUP); - - if (s != NULL && SYMBOL_TYPE (s) != TOKEN_VOID) - result = ARG (2); - else if (argc >= 4) - result = ARG (3); - else - result = NULL; - - if (result != NULL) - obstack_grow (obs, result, strlen (result)); -} - -static void -m4_ifelse (struct obstack *obs, int argc, token_data **argv) -{ - const char *result; - token_data *me = argv[0]; - - if (argc == 2) - return; - - if (bad_argc (me, argc, 4, -1)) - return; - else - /* Diagnose excess arguments if 5, 8, 11, etc., actual arguments. */ - bad_argc (me, (argc + 2) % 3, -1, 1); - - argv++; - argc--; - - result = NULL; - while (result == NULL) - - if (STREQ (ARG (0), ARG (1))) - result = ARG (2); - - else - switch (argc) - { - case 3: - return; - - case 4: - case 5: - result = ARG (3); - break; - - default: - argc -= 3; - argv += 3; - } - - obstack_grow (obs, result, strlen (result)); -} - -/*-------------------------------------------------------------------. -| The function dump_symbol () is for use by "dumpdef". It builds up | -| a table of all defined, un-shadowed, symbols. | -`-------------------------------------------------------------------*/ - -/* The structure dump_symbol_data is used to pass the information needed - from call to call to dump_symbol. */ - -struct dump_symbol_data -{ - struct obstack *obs; /* obstack for table */ - symbol **base; /* base of table */ - int size; /* size of table */ -}; - -static void -dump_symbol (symbol *sym, void *arg) -{ - struct dump_symbol_data *data = (struct dump_symbol_data *) arg; - if (!SYMBOL_SHADOWED (sym) && SYMBOL_TYPE (sym) != TOKEN_VOID) - { - obstack_blank (data->obs, sizeof (symbol *)); - data->base = (symbol **) obstack_base (data->obs); - data->base[data->size++] = sym; - } -} - -/*------------------------------------------------------------------------. -| qsort comparison routine, for sorting the table made in m4_dumpdef (). | -`------------------------------------------------------------------------*/ - -static int -dumpdef_cmp (const void *s1, const void *s2) -{ - return strcmp (SYMBOL_NAME (* (symbol *const *) s1), - SYMBOL_NAME (* (symbol *const *) s2)); -} - -/*-------------------------------------------------------------. -| Implementation of "dumpdef" itself. It builds up a table of | -| pointers to symbols, sorts it and prints the sorted table. | -`-------------------------------------------------------------*/ - -static void -m4_dumpdef (struct obstack *obs, int argc, token_data **argv) -{ - symbol *s; - int i; - struct dump_symbol_data data; - const builtin *bp; - - data.obs = obs; - data.base = (symbol **) obstack_base (obs); - data.size = 0; - - if (argc == 1) - { - hack_all_symbols (dump_symbol, &data); - } - else - { - for (i = 1; i < argc; i++) - { - s = lookup_symbol (TOKEN_DATA_TEXT (argv[i]), SYMBOL_LOOKUP); - if (s != NULL && SYMBOL_TYPE (s) != TOKEN_VOID) - dump_symbol (s, &data); - else - M4ERROR ((warning_status, 0, - "undefined macro `%s'", TOKEN_DATA_TEXT (argv[i]))); - } - } - - /* Make table of symbols invisible to expand_macro (). */ - - obstack_finish (obs); - - qsort (data.base, data.size, sizeof (symbol *), dumpdef_cmp); - - for (; data.size > 0; --data.size, data.base++) - { - DEBUG_PRINT1 ("%s:\t", SYMBOL_NAME (data.base[0])); - - switch (SYMBOL_TYPE (data.base[0])) - { - case TOKEN_TEXT: - if (debug_level & DEBUG_TRACE_QUOTE) - DEBUG_PRINT3 ("%s%s%s\n", - lquote.string, SYMBOL_TEXT (data.base[0]), rquote.string); - else - DEBUG_PRINT1 ("%s\n", SYMBOL_TEXT (data.base[0])); - break; - - case TOKEN_FUNC: - bp = find_builtin_by_addr (SYMBOL_FUNC (data.base[0])); - if (bp == NULL) - { - M4ERROR ((warning_status, 0, "\ -INTERNAL ERROR: builtin not found in builtin table")); - abort (); - } - DEBUG_PRINT1 ("<%s>\n", bp->name); - break; - - case TOKEN_VOID: - default: - M4ERROR ((warning_status, 0, - "INTERNAL ERROR: bad token data type in m4_dumpdef ()")); - abort (); - break; - } - } -} - -/*-----------------------------------------------------------------. -| The builtin "builtin" allows calls to builtin macros, even if | -| their definition has been overridden or shadowed. It is thus | -| possible to redefine builtins, and still access their original | -| definition. This macro is not available in compatibility mode. | -`-----------------------------------------------------------------*/ - -static void -m4_builtin (struct obstack *obs, int argc, token_data **argv) -{ - const builtin *bp; - const char *name; - - if (bad_argc (argv[0], argc, 2, -1)) - return; - if (TOKEN_DATA_TYPE (argv[1]) != TOKEN_TEXT) - { - M4ERROR ((warning_status, 0, - "Warning: %s: invalid macro name ignored", ARG (0))); - return; - } - - name = ARG (1); - bp = find_builtin_by_name (name); - if (bp->func == m4_placeholder) - M4ERROR ((warning_status, 0, - "undefined builtin `%s'", name)); - else - { - int i; - if (! bp->groks_macro_args) - for (i = 2; i < argc; i++) - if (TOKEN_DATA_TYPE (argv[i]) != TOKEN_TEXT) - { - TOKEN_DATA_TYPE (argv[i]) = TOKEN_TEXT; - TOKEN_DATA_TEXT (argv[i]) = (char *) ""; - } - bp->func (obs, argc - 1, argv + 1); - } -} - -/*-------------------------------------------------------------------. -| The builtin "indir" allows indirect calls to macros, even if their | -| name is not a proper macro name. It is thus possible to define | -| macros with ill-formed names for internal use in larger macro | -| packages. This macro is not available in compatibility mode. | -`-------------------------------------------------------------------*/ - -static void -m4_indir (struct obstack *obs, int argc, token_data **argv) -{ - symbol *s; - const char *name; - - if (bad_argc (argv[0], argc, 2, -1)) - return; - if (TOKEN_DATA_TYPE (argv[1]) != TOKEN_TEXT) - { - M4ERROR ((warning_status, 0, - "Warning: %s: invalid macro name ignored", ARG (0))); - return; - } - - name = ARG (1); - s = lookup_symbol (name, SYMBOL_LOOKUP); - if (s == NULL || SYMBOL_TYPE (s) == TOKEN_VOID) - M4ERROR ((warning_status, 0, - "undefined macro `%s'", name)); - else - { - int i; - if (! SYMBOL_MACRO_ARGS (s)) - for (i = 2; i < argc; i++) - if (TOKEN_DATA_TYPE (argv[i]) != TOKEN_TEXT) - { - TOKEN_DATA_TYPE (argv[i]) = TOKEN_TEXT; - TOKEN_DATA_TEXT (argv[i]) = (char *) ""; - } - call_macro (s, argc - 1, argv + 1, obs); - } -} - -/*------------------------------------------------------------------. -| The macro "defn" returns the quoted definition of the macro named | -| by the first argument. If the macro is builtin, it will push a | -| special macro-definition token on the input stack. | -`------------------------------------------------------------------*/ - -static void -m4_defn (struct obstack *obs, int argc, token_data **argv) -{ - symbol *s; - builtin_func *b; - unsigned int i; - - if (bad_argc (argv[0], argc, 2, -1)) - return; - - assert (0 < argc); - for (i = 1; i < (unsigned) argc; i++) - { - const char *arg = ARG((int) i); - s = lookup_symbol (arg, SYMBOL_LOOKUP); - if (s == NULL) - continue; - - switch (SYMBOL_TYPE (s)) - { - case TOKEN_TEXT: - obstack_grow (obs, lquote.string, lquote.length); - obstack_grow (obs, SYMBOL_TEXT (s), strlen (SYMBOL_TEXT (s))); - obstack_grow (obs, rquote.string, rquote.length); - break; - - case TOKEN_FUNC: - b = SYMBOL_FUNC (s); - if (b == m4_placeholder) - M4ERROR ((warning_status, 0, "\ -builtin `%s' requested by frozen file is not supported", arg)); - else if (argc != 2) - M4ERROR ((warning_status, 0, - "Warning: cannot concatenate builtin `%s'", - arg)); - else - push_macro (b); - break; - - case TOKEN_VOID: - /* Nothing to do for traced but undefined macro. */ - break; - - default: - M4ERROR ((warning_status, 0, - "INTERNAL ERROR: bad symbol type in m4_defn ()")); - abort (); - } - } -} - -/*--------------------------------------------------------------. -| This section contains macros to handle the builtins "syscmd", | -| "esyscmd" and "sysval". "esyscmd" is GNU specific. | -`--------------------------------------------------------------*/ - -/* Exit code from last "syscmd" command. */ -static int sysval; - -static void -m4_syscmd (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) -{ - const char *cmd = ARG (1); - int status; - int sig_status; - const char *prog_args[4] = { "sh", "-c" }; - if (bad_argc (argv[0], argc, 2, 2) || !*cmd) - { - /* The empty command is successful. */ - sysval = 0; - return; - } - - debug_flush_files (); -#if W32_NATIVE - if (strstr (SYSCMD_SHELL, "cmd")) - { - prog_args[0] = "cmd"; - prog_args[1] = "/c"; - } -#endif - prog_args[2] = cmd; - errno = 0; - status = execute (ARG (0), SYSCMD_SHELL, (char **) prog_args, false, - false, false, false, true, false, &sig_status); - if (sig_status) - { - assert (status == 127); - sysval = sig_status << 8; - } - else - { - if (status == 127 && errno) - M4ERROR ((warning_status, errno, "cannot run command `%s'", cmd)); - sysval = status; - } -} - -static void -m4_esyscmd (struct obstack *obs, int argc, token_data **argv) -{ - const char *cmd = ARG (1); - const char *prog_args[4] = { "sh", "-c" }; - pid_t child; - int fd; - FILE *pin; - int status; - int sig_status; - - if (bad_argc (argv[0], argc, 2, 2) || !*cmd) - { - /* The empty command is successful. */ - sysval = 0; - return; - } - - debug_flush_files (); -#if W32_NATIVE - if (strstr (SYSCMD_SHELL, "cmd")) - { - prog_args[0] = "cmd"; - prog_args[1] = "/c"; - } -#endif - prog_args[2] = cmd; - errno = 0; - child = create_pipe_in (ARG (0), SYSCMD_SHELL, (char **) prog_args, - NULL, false, true, false, &fd); - if (child == -1) - { - M4ERROR ((warning_status, errno, "cannot run command `%s'", cmd)); - sysval = 127; - return; - } - pin = fdopen (fd, "r"); - if (pin == NULL) - { - M4ERROR ((warning_status, errno, "cannot run command `%s'", cmd)); - sysval = 127; - close (fd); - return; - } - while (1) - { - size_t avail = obstack_room (obs); - size_t len; - if (!avail) - { - int ch = getc (pin); - if (ch == EOF) - break; - obstack_1grow (obs, ch); - continue; - } - len = fread (obstack_next_free (obs), 1, avail, pin); - if (len <= 0) - break; - obstack_blank_fast (obs, len); - } - if (ferror (pin) || fclose (pin)) - M4ERROR ((EXIT_FAILURE, errno, "cannot read pipe")); - errno = 0; - status = wait_subprocess (child, ARG (0), false, true, true, false, - &sig_status); - if (sig_status) - { - assert (status == 127); - sysval = sig_status << 8; - } - else - { - if (status == 127 && errno) - M4ERROR ((warning_status, errno, "cannot run command `%s'", cmd)); - sysval = status; - } -} - -static void -m4_sysval (struct obstack *obs, int argc M4_GNUC_UNUSED, - token_data **argv M4_GNUC_UNUSED) -{ - shipout_int (obs, sysval); -} - -/*------------------------------------------------------------------. -| This section contains the top level code for the "eval" builtin. | -| The actual work is done in the function evaluate (), which lives | -| in eval.c. | -`------------------------------------------------------------------*/ - -static void -m4_eval (struct obstack *obs, int argc, token_data **argv) -{ - int32_t value = 0; - int radix = 10; - int min = 1; - const char *s; - - if (bad_argc (argv[0], argc, 2, 4)) - return; - - if (*ARG (2) && !numeric_arg (argv[0], ARG (2), &radix)) - return; - - if (radix < 1 || radix > (int) strlen (digits)) - { - M4ERROR ((warning_status, 0, - "radix %d in builtin `%s' out of range", - radix, ARG (0))); - return; - } - - if (argc >= 4 && !numeric_arg (argv[0], ARG (3), &min)) - return; - if (min < 0) - { - M4ERROR ((warning_status, 0, - "negative width to builtin `%s'", ARG (0))); - return; - } - - if (!*ARG (1)) - M4ERROR ((warning_status, 0, - "empty string treated as 0 in builtin `%s'", ARG (0))); - else if (evaluate (ARG (1), &value)) - return; - - if (radix == 1) - { - if (value < 0) - { - obstack_1grow (obs, '-'); - value = -value; - } - /* This assumes 2's-complement for correctly handling INT_MIN. */ - while (min-- - value > 0) - obstack_1grow (obs, '0'); - while (value-- != 0) - obstack_1grow (obs, '1'); - obstack_1grow (obs, '\0'); - return; - } - - s = ntoa (value, radix); - - if (*s == '-') - { - obstack_1grow (obs, '-'); - s++; - } - for (min -= strlen (s); --min >= 0;) - obstack_1grow (obs, '0'); - - obstack_grow (obs, s, strlen (s)); -} - -static void -m4_incr (struct obstack *obs, int argc, token_data **argv) -{ - int value; - - if (bad_argc (argv[0], argc, 2, 2)) - return; - - if (!numeric_arg (argv[0], ARG (1), &value)) - return; - - shipout_int (obs, value + 1); -} - -static void -m4_decr (struct obstack *obs, int argc, token_data **argv) -{ - int value; - - if (bad_argc (argv[0], argc, 2, 2)) - return; - - if (!numeric_arg (argv[0], ARG (1), &value)) - return; - - shipout_int (obs, value - 1); -} - -/* This section contains the macros "divert", "undivert" and "divnum" for - handling diversion. The utility functions used lives in output.c. */ - -/*-----------------------------------------------------------------. -| Divert further output to the diversion given by ARGV[1]. Out of | -| range means discard further output. | -`-----------------------------------------------------------------*/ - -static void -m4_divert (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) -{ - int i = 0; - - if (bad_argc (argv[0], argc, 1, 2)) - return; - - if (argc >= 2 && !numeric_arg (argv[0], ARG (1), &i)) - return; - - make_diversion (i); -} - -/*-----------------------------------------------------. -| Expand to the current diversion number, -1 if none. | -`-----------------------------------------------------*/ - -static void -m4_divnum (struct obstack *obs, int argc, token_data **argv) -{ - if (bad_argc (argv[0], argc, 1, 1)) - return; - shipout_int (obs, current_diversion); -} - -/*------------------------------------------------------------------. -| Bring back the diversion given by the argument list. If none is | -| specified, bring back all diversions. GNU specific is the option | -| of undiverting named files, by passing a non-numeric argument to | -| undivert (). | -`------------------------------------------------------------------*/ - -static void -m4_undivert (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) -{ - int i, file; - FILE *fp; - char *endp; - - if (argc == 1) - undivert_all (); - else - for (i = 1; i < argc; i++) - { - file = strtol (ARG (i), &endp, 10); - if (*endp == '\0' && !isspace (to_uchar (*ARG (i)))) - insert_diversion (file); - else if (no_gnu_extensions) - M4ERROR ((warning_status, 0, - "non-numeric argument to builtin `%s'", ARG (0))); - else - { - fp = m4_path_search (ARG (i), NULL); - if (fp != NULL) - { - insert_file (fp); - if (fclose (fp) == EOF) - M4ERROR ((warning_status, errno, - "error undiverting `%s'", ARG (i))); - } - else - M4ERROR ((warning_status, errno, - "cannot undivert `%s'", ARG (i))); - } - } -} - -/* This section contains various macros, which does not fall into any - specific group. These are "dnl", "shift", "changequote", "changecom" - and "changeword". */ - -/*-----------------------------------------------------------. -| Delete all subsequent whitespace from input. The function | -| skip_line () lives in input.c. | -`-----------------------------------------------------------*/ - -static void -m4_dnl (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) -{ - if (bad_argc (argv[0], argc, 1, 1)) - return; - - skip_line (); -} - -/*--------------------------------------------------------------------. -| Shift all arguments one to the left, discarding the first | -| argument. Each output argument is quoted with the current quotes. | -`--------------------------------------------------------------------*/ - -static void -m4_shift (struct obstack *obs, int argc, token_data **argv) -{ - if (bad_argc (argv[0], argc, 2, -1)) - return; - dump_args (obs, argc - 1, argv + 1, ",", true); -} - -/*--------------------------------------------------------------------------. -| Change the current quotes. The function set_quotes () lives in input.c. | -`--------------------------------------------------------------------------*/ - -static void -m4_changequote (struct obstack *obs M4_GNUC_UNUSED, int argc, - token_data **argv) -{ - if (bad_argc (argv[0], argc, 1, 3)) - return; - - /* Explicit NULL distinguishes between empty and missing argument. */ - set_quotes ((argc >= 2) ? TOKEN_DATA_TEXT (argv[1]) : NULL, - (argc >= 3) ? TOKEN_DATA_TEXT (argv[2]) : NULL); -} - -/*-----------------------------------------------------------------. -| Change the current comment delimiters. The function set_comment | -| () lives in input.c. | -`-----------------------------------------------------------------*/ - -static void -m4_changecom (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) -{ - if (bad_argc (argv[0], argc, 1, 3)) - return; - - /* Explicit NULL distinguishes between empty and missing argument. */ - set_comment ((argc >= 2) ? TOKEN_DATA_TEXT (argv[1]) : NULL, - (argc >= 3) ? TOKEN_DATA_TEXT (argv[2]) : NULL); -} - -#ifdef ENABLE_CHANGEWORD - -/*---------------------------------------------------------------. -| Change the regular expression used for breaking the input into | -| words. The function set_word_regexp () lives in input.c. | -`---------------------------------------------------------------*/ - -static void -m4_changeword (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) -{ - if (bad_argc (argv[0], argc, 2, 2)) - return; - - set_word_regexp (TOKEN_DATA_TEXT (argv[1])); -} - -#endif /* ENABLE_CHANGEWORD */ - -/* This section contains macros for inclusion of other files -- "include" - and "sinclude". This differs from bringing back diversions, in that - the input is scanned before being copied to the output. */ - -/*---------------------------------------------------------------. -| Generic include function. Include the file given by the first | -| argument, if it exists. Complain about inaccessible files iff | -| SILENT is false. | -`---------------------------------------------------------------*/ - -static void -include (int argc, token_data **argv, bool silent) -{ - FILE *fp; - char *name; - - if (bad_argc (argv[0], argc, 2, 2)) - return; - - fp = m4_path_search (ARG (1), &name); - if (fp == NULL) - { - if (!silent) - { - M4ERROR ((warning_status, errno, "cannot open `%s'", ARG (1))); - retcode = EXIT_FAILURE; - } - return; - } - - push_file (fp, name, true); - free (name); -} - -/*------------------------------------------------. -| Include a file, complaining in case of errors. | -`------------------------------------------------*/ - -static void -m4_include (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) -{ - include (argc, argv, false); -} - -/*----------------------------------. -| Include a file, ignoring errors. | -`----------------------------------*/ - -static void -m4_sinclude (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) -{ - include (argc, argv, true); -} - -/* More miscellaneous builtins -- "maketemp", "errprint", "__file__", - "__line__", and "__program__". The last three are GNU specific. */ - -/*------------------------------------------------------------------. -| Use the first argument as at template for a temporary file name. | -`------------------------------------------------------------------*/ - -/* Add trailing 'X' to PATTERN of length LEN as necessary, then - securely create the file, and place the quoted new file name on - OBS. Report errors on behalf of ME. */ -static void -mkstemp_helper (struct obstack *obs, const char *me, const char *pattern, - size_t len) -{ - int fd; - size_t i; - char *name; - - /* Guarantee that there are six trailing 'X' characters, even if the - user forgot to supply them. Output must be quoted if - successful. */ - obstack_grow (obs, lquote.string, lquote.length); - obstack_grow (obs, pattern, len); - for (i = 0; len > 0 && i < 6; i++) - if (pattern[len - i - 1] != 'X') - break; - obstack_grow0 (obs, "XXXXXX", 6 - i); - name = (char *) obstack_base (obs) + lquote.length; - - errno = 0; - fd = mkstemp (name); - if (fd < 0) - { - M4ERROR ((0, errno, "%s: cannot create tempfile `%s'", me, pattern)); - obstack_free (obs, obstack_finish (obs)); - } - else - { - close (fd); - /* Remove NUL, then finish quote. */ - obstack_blank (obs, -1); - obstack_grow (obs, rquote.string, rquote.length); - } -} - -static void -m4_maketemp (struct obstack *obs, int argc, token_data **argv) -{ - if (bad_argc (argv[0], argc, 2, 2)) - return; - if (no_gnu_extensions) - { - /* POSIX states "any trailing 'X' characters [are] replaced with - the current process ID as a string", without referencing the - file system. Horribly insecure, but we have to do it when we - are in traditional mode. - - For reference, Solaris m4 does: - maketemp() -> `' - maketemp(X) -> `X' - maketemp(XX) -> `Xn', where n is last digit of pid - maketemp(XXXXXXXX) -> `X00nnnnn', where nnnnn is 16-bit pid - */ - const char *str = ARG (1); - int len = strlen (str); - int i; - int len2; - - M4ERROR ((warning_status, 0, "recommend using mkstemp instead")); - for (i = len; i > 1; i--) - if (str[i - 1] != 'X') - break; - obstack_grow (obs, str, i); - str = ntoa ((int32_t) getpid (), 10); - len2 = strlen (str); - if (len2 > len - i) - obstack_grow0 (obs, str + len2 - (len - i), len - i); - else - { - while (i++ < len - len2) - obstack_1grow (obs, '0'); - obstack_grow0 (obs, str, len2); - } - } - else - mkstemp_helper (obs, ARG (0), ARG (1), strlen (ARG (1))); -} - -static void -m4_mkstemp (struct obstack *obs, int argc, token_data **argv) -{ - if (bad_argc (argv[0], argc, 2, 2)) - return; - mkstemp_helper (obs, ARG (0), ARG (1), strlen (ARG (1))); -} - -/*----------------------------------------. -| Print all arguments on standard error. | -`----------------------------------------*/ - -static void -m4_errprint (struct obstack *obs, int argc, token_data **argv) -{ - if (bad_argc (argv[0], argc, 2, -1)) - return; - dump_args (obs, argc, argv, " ", false); - obstack_1grow (obs, '\0'); - debug_flush_files (); - xfprintf (stderr, "%s", (char *) obstack_finish (obs)); - fflush (stderr); -} - -static void -m4___file__ (struct obstack *obs, int argc, token_data **argv) -{ - if (bad_argc (argv[0], argc, 1, 1)) - return; - obstack_grow (obs, lquote.string, lquote.length); - obstack_grow (obs, current_file, strlen (current_file)); - obstack_grow (obs, rquote.string, rquote.length); -} - -static void -m4___line__ (struct obstack *obs, int argc, token_data **argv) -{ - if (bad_argc (argv[0], argc, 1, 1)) - return; - shipout_int (obs, current_line); -} - -static void -m4___program__ (struct obstack *obs, int argc, token_data **argv) -{ - if (bad_argc (argv[0], argc, 1, 1)) - return; - obstack_grow (obs, lquote.string, lquote.length); - obstack_grow (obs, program_name, strlen (program_name)); - obstack_grow (obs, rquote.string, rquote.length); -} - -/* This section contains various macros for exiting, saving input until - EOF is seen, and tracing macro calls. That is: "m4exit", "m4wrap", - "traceon" and "traceoff". */ - -/*----------------------------------------------------------. -| Exit immediately, with exit status specified by the first | -| argument, or 0 if no arguments are present. | -`----------------------------------------------------------*/ - -static void M4_GNUC_NORETURN -m4_m4exit (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) -{ - int exit_code = EXIT_SUCCESS; - - /* Warn on bad arguments, but still exit. */ - bad_argc (argv[0], argc, 1, 2); - if (argc >= 2 && !numeric_arg (argv[0], ARG (1), &exit_code)) - exit_code = EXIT_FAILURE; - if (exit_code < 0 || exit_code > 255) - { - M4ERROR ((warning_status, 0, - "exit status out of range: `%d'", exit_code)); - exit_code = EXIT_FAILURE; - } - /* Change debug stream back to stderr, to force flushing debug stream and - detect any errors it might have encountered. */ - debug_set_output (NULL); - debug_flush_files (); - if (exit_code == EXIT_SUCCESS && retcode != EXIT_SUCCESS) - exit_code = retcode; - /* Propagate non-zero status to atexit handlers. */ - if (exit_code != EXIT_SUCCESS) - exit_failure = exit_code; - exit (exit_code); -} - -/*------------------------------------------------------------------. -| Save the argument text until EOF has been seen, allowing for user | -| specified cleanup action. GNU version saves all arguments, the | -| standard version only the first. | -`------------------------------------------------------------------*/ - -static void -m4_m4wrap (struct obstack *obs, int argc, token_data **argv) -{ - if (bad_argc (argv[0], argc, 2, -1)) - return; - if (no_gnu_extensions) - obstack_grow (obs, ARG (1), strlen (ARG (1))); - else - dump_args (obs, argc, argv, " ", false); - obstack_1grow (obs, '\0'); - push_wrapup ((char *) obstack_finish (obs)); -} - -/* Enable tracing of all specified macros, or all, if none is specified. - Tracing is disabled by default, when a macro is defined. This can be - overridden by the "t" debug flag. */ - -/*------------------------------------------------------------------. -| Set_trace () is used by "traceon" and "traceoff" to enable and | -| disable tracing of a macro. It disables tracing if DATA is NULL, | -| otherwise it enables tracing. | -`------------------------------------------------------------------*/ - -static void -set_trace (symbol *sym, void *data) -{ - SYMBOL_TRACED (sym) = data != NULL; - /* Remove placeholder from table if macro is undefined and untraced. */ - if (SYMBOL_TYPE (sym) == TOKEN_VOID && data == NULL) - lookup_symbol (SYMBOL_NAME (sym), SYMBOL_POPDEF); -} - -static void -m4_traceon (struct obstack *obs, int argc, token_data **argv) -{ - symbol *s; - int i; - - if (argc == 1) - hack_all_symbols (set_trace, obs); - else - for (i = 1; i < argc; i++) - { - s = lookup_symbol (ARG (i), SYMBOL_LOOKUP); - if (!s) - s = lookup_symbol (ARG (i), SYMBOL_INSERT); - set_trace (s, obs); - } -} - -/*------------------------------------------------------------------------. -| Disable tracing of all specified macros, or all, if none is specified. | -`------------------------------------------------------------------------*/ - -static void -m4_traceoff (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) -{ - symbol *s; - int i; - - if (argc == 1) - hack_all_symbols (set_trace, NULL); - else - for (i = 1; i < argc; i++) - { - s = lookup_symbol (TOKEN_DATA_TEXT (argv[i]), SYMBOL_LOOKUP); - if (s != NULL) - set_trace (s, NULL); - } -} - -/*------------------------------------------------------------------. -| On-the-fly control of the format of the tracing output. It takes | -| one argument, which is a character string like given to the -d | -| option, or none in which case the debug_level is zeroed. | -`------------------------------------------------------------------*/ - -static void -m4_debugmode (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) -{ - int new_debug_level; - int change_flag; - - if (bad_argc (argv[0], argc, 1, 2)) - return; - - if (argc == 1) - debug_level = 0; - else - { - if (ARG (1)[0] == '+' || ARG (1)[0] == '-') - { - change_flag = ARG (1)[0]; - new_debug_level = debug_decode (ARG (1) + 1); - } - else - { - change_flag = 0; - new_debug_level = debug_decode (ARG (1)); - } - - if (new_debug_level < 0) - M4ERROR ((warning_status, 0, - "Debugmode: bad debug flags: `%s'", ARG (1))); - else - { - switch (change_flag) - { - case 0: - debug_level = new_debug_level; - break; - - case '+': - debug_level |= new_debug_level; - break; - - case '-': - debug_level &= ~new_debug_level; - break; - - default: - M4ERROR ((warning_status, 0, - "INTERNAL ERROR: bad flag in m4_debugmode ()")); - abort (); - } - } - } -} - -/*-------------------------------------------------------------------------. -| Specify the destination of the debugging output. With one argument, the | -| argument is taken as a file name, with no arguments, revert to stderr. | -`-------------------------------------------------------------------------*/ - -static void -m4_debugfile (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) -{ - if (bad_argc (argv[0], argc, 1, 2)) - return; - - if (argc == 1) - debug_set_output (NULL); - else if (!debug_set_output (ARG (1))) - M4ERROR ((warning_status, errno, - "cannot set debug file `%s'", ARG (1))); -} - -/* This section contains text processing macros: "len", "index", - "substr", "translit", "format", "regexp" and "patsubst". The last - three are GNU specific. */ - -/*---------------------------------------------. -| Expand to the length of the first argument. | -`---------------------------------------------*/ - -static void -m4_len (struct obstack *obs, int argc, token_data **argv) -{ - if (bad_argc (argv[0], argc, 2, 2)) - return; - shipout_int (obs, strlen (ARG (1))); -} - -/*-------------------------------------------------------------------. -| The macro expands to the first index of the second argument in the | -| first argument. | -`-------------------------------------------------------------------*/ - -static void -m4_index (struct obstack *obs, int argc, token_data **argv) -{ - const char *haystack; - const char *result; - int retval; - - if (bad_argc (argv[0], argc, 3, 3)) - { - /* builtin(`index') is blank, but index(`abc') is 0. */ - if (argc == 2) - shipout_int (obs, 0); - return; - } - - haystack = ARG (1); - result = strstr (haystack, ARG (2)); - retval = result ? result - haystack : -1; - - shipout_int (obs, retval); -} - -/*-----------------------------------------------------------------. -| The macro "substr" extracts substrings from the first argument, | -| starting from the index given by the second argument, extending | -| for a length given by the third argument. If the third argument | -| is missing, the substring extends to the end of the first | -| argument. | -`-----------------------------------------------------------------*/ - -static void -m4_substr (struct obstack *obs, int argc, token_data **argv) -{ - int start = 0; - int length, avail; - - if (bad_argc (argv[0], argc, 3, 4)) - { - /* builtin(`substr') is blank, but substr(`abc') is abc. */ - if (argc == 2) - obstack_grow (obs, ARG (1), strlen (ARG (1))); - return; - } - - length = avail = strlen (ARG (1)); - if (!numeric_arg (argv[0], ARG (2), &start)) - return; - - if (argc >= 4 && !numeric_arg (argv[0], ARG (3), &length)) - return; - - if (start < 0 || length <= 0 || start >= avail) - return; - - if (start + length > avail) - length = avail - start; - obstack_grow (obs, ARG (1) + start, length); -} - -/*------------------------------------------------------------------. -| For "translit", ranges are allowed in the second and third | -| argument. They are expanded in the following function, and the | -| expanded strings, without any ranges left, are used to translate | -| the characters of the first argument. A single - (dash) can be | -| included in the strings by being the first or the last character | -| in the string. If the first character in a range is after the | -| first in the character set, the range is made backwards, thus 9-0 | -| is the string 9876543210. | -`------------------------------------------------------------------*/ - -static const char * -expand_ranges (const char *s, struct obstack *obs) -{ - unsigned char from; - unsigned char to; - - for (from = '\0'; *s != '\0'; from = to_uchar (*s++)) - { - if (*s == '-' && from != '\0') - { - to = to_uchar (*++s); - if (to == '\0') - { - /* trailing dash */ - obstack_1grow (obs, '-'); - break; - } - else if (from <= to) - { - while (from++ < to) - obstack_1grow (obs, from); - } - else - { - while (--from >= to) - obstack_1grow (obs, from); - } - } - else - obstack_1grow (obs, *s); - } - obstack_1grow (obs, '\0'); - return (char *) obstack_finish (obs); -} - -/*-----------------------------------------------------------------. -| The macro "translit" translates all characters in the first | -| argument, which are present in the second argument, into the | -| corresponding character from the third argument. If the third | -| argument is shorter than the second, the extra characters in the | -| second argument are deleted from the first. | -`-----------------------------------------------------------------*/ - -static void -m4_translit (struct obstack *obs, int argc, token_data **argv) -{ - const char *data = ARG (1); - const char *from = ARG (2); - const char *to; - char map[UCHAR_MAX + 1]; - char found[UCHAR_MAX + 1]; - unsigned char ch; - - if (bad_argc (argv[0], argc, 3, 4) || !*data || !*from) - { - /* builtin(`translit') is blank, but translit(`abc') is abc. */ - if (2 <= argc) - obstack_grow (obs, data, strlen (data)); - return; - } - - to = ARG (3); - if (strchr (to, '-') != NULL) - { - to = expand_ranges (to, obs); - assert (to && *to); - } - - /* If there are only one or two bytes to replace, it is faster to - use memchr2. Using expand_ranges does nothing unless there are - at least three bytes. */ - if (!from[1] || !from[2]) - { - const char *p; - size_t len = strlen (data); - while ((p = (char *) memchr2 (data, from[0], from[1], len))) - { - obstack_grow (obs, data, p - data); - len -= p - data; - if (!len) - return; - data = p + 1; - len--; - if (*p == from[0] && to[0]) - obstack_1grow (obs, to[0]); - else if (*p == from[1] && to[0] && to[1]) - obstack_1grow (obs, to[1]); - } - obstack_grow (obs, data, len); - return; - } - - if (strchr (from, '-') != NULL) - { - from = expand_ranges (from, obs); - assert (from && *from); - } - - /* Calling strchr(from) for each character in data is quadratic, - since both strings can be arbitrarily long. Instead, create a - from-to mapping in one pass of from, then use that map in one - pass of data, for linear behavior. Traditional behavior is that - only the first instance of a character in from is consulted, - hence the found map. */ - memset (map, 0, sizeof map); - memset (found, 0, sizeof found); - for ( ; (ch = *from) != '\0'; from++) - { - if (! found[ch]) - { - found[ch] = 1; - map[ch] = *to; - } - if (*to != '\0') - to++; - } - - for (data = ARG (1); (ch = *data) != '\0'; data++) - { - if (! found[ch]) - obstack_1grow (obs, ch); - else if (map[ch]) - obstack_1grow (obs, map[ch]); - } -} - -/*-------------------------------------------------------------------. -| Frontend for printf like formatting. The function format () lives | -| in the file format.c. | -`-------------------------------------------------------------------*/ - -static void -m4_format (struct obstack *obs, int argc, token_data **argv) -{ - if (bad_argc (argv[0], argc, 2, -1)) - return; - expand_format (obs, argc - 1, argv + 1); -} - -/*------------------------------------------------------------------. -| Function to perform substitution by regular expressions. Used by | -| the builtins regexp and patsubst. The changed text is placed on | -| the obstack. The substitution is REPL, with \& substituted by | -| this part of VICTIM matched by the last whole regular expression, | -| taken from REGS[0], and \N substituted by the text matched by the | -| Nth parenthesized sub-expression, taken from REGS[N]. | -`------------------------------------------------------------------*/ - -static int substitute_warned = 0; - -static void -substitute (struct obstack *obs, const char *victim, const char *repl, - struct re_registers *regs) -{ - int ch; - __re_size_t ind; - while (1) - { - const char *backslash = strchr (repl, '\\'); - if (!backslash) - { - obstack_grow (obs, repl, strlen (repl)); - return; - } - obstack_grow (obs, repl, backslash - repl); - repl = backslash; - ch = *++repl; - switch (ch) - { - case '0': - if (!substitute_warned) - { - M4ERROR ((warning_status, 0, "\ -Warning: \\0 will disappear, use \\& instead in replacements")); - substitute_warned = 1; - } - /* Fall through. */ - - case '&': - obstack_grow (obs, victim + regs->start[0], - regs->end[0] - regs->start[0]); - repl++; - break; - - case '1': case '2': case '3': case '4': case '5': case '6': - case '7': case '8': case '9': - ind = ch -= '0'; - if (regs->num_regs - 1 <= ind) - M4ERROR ((warning_status, 0, - "Warning: sub-expression %d not present", ch)); - else if (regs->end[ch] > 0) - obstack_grow (obs, victim + regs->start[ch], - regs->end[ch] - regs->start[ch]); - repl++; - break; - - case '\0': - M4ERROR ((warning_status, 0, - "Warning: trailing \\ ignored in replacement")); - return; - - default: - obstack_1grow (obs, ch); - repl++; - break; - } - } -} - -/*------------------------------------------. -| Initialize regular expression variables. | -`------------------------------------------*/ - -void -init_pattern_buffer (struct re_pattern_buffer *buf, struct re_registers *regs) -{ - buf->translate = NULL; - buf->fastmap = NULL; - buf->buffer = NULL; - buf->allocated = 0; - if (regs) - { - regs->start = NULL; - regs->end = NULL; - } -} - -/*------------------------------------------------------------------. -| Regular expression version of index. Given two arguments, expand | -| to the index of the first match of the second argument (a regexp) | -| in the first. Expand to -1 if here is no match. Given a third | -| argument, it changes the expansion to this argument. | -`------------------------------------------------------------------*/ - -static void -m4_regexp (struct obstack *obs, int argc, token_data **argv) -{ - const char *victim; /* first argument */ - const char *regexp; /* regular expression */ - const char *repl; /* replacement string */ - - struct re_pattern_buffer buf; /* compiled regular expression */ - struct re_registers regs; /* for subexpression matches */ - const char *msg; /* error message from re_compile_pattern */ - int startpos; /* start position of match */ - int length; /* length of first argument */ - - if (bad_argc (argv[0], argc, 3, 4)) - { - /* builtin(`regexp') is blank, but regexp(`abc') is 0. */ - if (argc == 2) - shipout_int (obs, 0); - return; - } - - victim = TOKEN_DATA_TEXT (argv[1]); - regexp = TOKEN_DATA_TEXT (argv[2]); - - init_pattern_buffer (&buf, ®s); - msg = re_compile_pattern (regexp, strlen (regexp), &buf); - - if (msg != NULL) - { - M4ERROR ((warning_status, 0, - "bad regular expression: `%s': %s", regexp, msg)); - free_pattern_buffer (&buf, ®s); - return; - } - - length = strlen (victim); - /* Avoid overhead of allocating regs if we won't use it. */ - startpos = re_search (&buf, victim, length, 0, length, - argc == 3 ? NULL : ®s); - - if (startpos == -2) - M4ERROR ((warning_status, 0, - "error matching regular expression `%s'", regexp)); - else if (argc == 3) - shipout_int (obs, startpos); - else if (startpos >= 0) - { - repl = TOKEN_DATA_TEXT (argv[3]); - substitute (obs, victim, repl, ®s); - } - - free_pattern_buffer (&buf, ®s); -} - -/*--------------------------------------------------------------------------. -| Substitute all matches of a regexp occuring in a string. Each match of | -| the second argument (a regexp) in the first argument is changed to the | -| third argument, with \& substituted by the matched text, and \N | -| substituted by the text matched by the Nth parenthesized sub-expression. | -`--------------------------------------------------------------------------*/ - -static void -m4_patsubst (struct obstack *obs, int argc, token_data **argv) -{ - const char *victim; /* first argument */ - const char *regexp; /* regular expression */ - - struct re_pattern_buffer buf; /* compiled regular expression */ - struct re_registers regs; /* for subexpression matches */ - const char *msg; /* error message from re_compile_pattern */ - int matchpos; /* start position of match */ - int offset; /* current match offset */ - int length; /* length of first argument */ - - if (bad_argc (argv[0], argc, 3, 4)) - { - /* builtin(`patsubst') is blank, but patsubst(`abc') is abc. */ - if (argc == 2) - obstack_grow (obs, ARG (1), strlen (ARG (1))); - return; - } - - regexp = TOKEN_DATA_TEXT (argv[2]); - - init_pattern_buffer (&buf, ®s); - msg = re_compile_pattern (regexp, strlen (regexp), &buf); - - if (msg != NULL) - { - M4ERROR ((warning_status, 0, - "bad regular expression `%s': %s", regexp, msg)); - free (buf.buffer); - return; - } - - victim = TOKEN_DATA_TEXT (argv[1]); - length = strlen (victim); - - offset = 0; - while (offset <= length) - { - matchpos = re_search (&buf, victim, length, - offset, length - offset, ®s); - if (matchpos < 0) - { - - /* Match failed -- either error or there is no match in the - rest of the string, in which case the rest of the string is - copied verbatim. */ - - if (matchpos == -2) - M4ERROR ((warning_status, 0, - "error matching regular expression `%s'", regexp)); - else if (offset < length) - obstack_grow (obs, victim + offset, length - offset); - break; - } - - /* Copy the part of the string that was skipped by re_search (). */ - - if (matchpos > offset) - obstack_grow (obs, victim + offset, matchpos - offset); - - /* Handle the part of the string that was covered by the match. */ - - substitute (obs, victim, ARG (3), ®s); - - /* Update the offset to the end of the match. If the regexp - matched a null string, advance offset one more, to avoid - infinite loops. */ - - offset = regs.end[0]; - if (regs.start[0] == regs.end[0]) - obstack_1grow (obs, victim[offset++]); - } - obstack_1grow (obs, '\0'); - - free_pattern_buffer (&buf, ®s); -} - -/* Finally, a placeholder builtin. This builtin is not installed by - default, but when reading back frozen files, this is associated - with any builtin we don't recognize (for example, if the frozen - file was created with a changeword capable m4, but is then loaded - by a different m4 that does not support changeword). This way, we - can keep 'm4 -R' quiet in the common case that the user did not - know or care about the builtin when the frozen file was created, - while still flagging it as a potential error if an attempt is made - to actually use the builtin. */ - -/*--------------------------------------------------------------------. -| Issue a warning that this macro is a placeholder for an unsupported | -| builtin that was requested while reloading a frozen file. | -`--------------------------------------------------------------------*/ - -void -m4_placeholder (struct obstack *obs M4_GNUC_UNUSED, int argc, - token_data **argv) -{ - M4ERROR ((warning_status, 0, "\ -builtin `%s' requested by frozen file is not supported", ARG (0))); -} - -/*-------------------------------------------------------------------. -| This function handles all expansion of user defined and predefined | -| macros. It is called with an obstack OBS, where the macros | -| expansion will be placed, as an unfinished object. SYM points to | -| the macro definition, giving the expansion text. ARGC and ARGV | -| are the arguments, as usual. | -`-------------------------------------------------------------------*/ - -void -expand_user_macro (struct obstack *obs, symbol *sym, - int argc, token_data **argv) -{ - const char *text = SYMBOL_TEXT (sym); - int i; - while (1) - { - const char *dollar = strchr (text, '$'); - if (!dollar) - { - obstack_grow (obs, text, strlen (text)); - return; - } - obstack_grow (obs, text, dollar - text); - text = dollar; - switch (*++text) - { - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - if (no_gnu_extensions) - { - i = *text++ - '0'; - } - else - { - for (i = 0; isdigit (to_uchar (*text)); text++) - i = i*10 + (*text - '0'); - } - if (i < argc) - obstack_grow (obs, TOKEN_DATA_TEXT (argv[i]), - strlen (TOKEN_DATA_TEXT (argv[i]))); - break; - - case '#': /* number of arguments */ - shipout_int (obs, argc - 1); - text++; - break; - - case '*': /* all arguments */ - case '@': /* ... same, but quoted */ - dump_args (obs, argc, argv, ",", *text == '@'); - text++; - break; - - default: - obstack_1grow (obs, '$'); - break; - } - } -} +#include "spawn-pipe.h" +#include "wait-process.h" + +#define ARG(i) (argc > (i) ? TOKEN_DATA_TEXT (argv[i]) : "") + +/* Initialization of builtin and predefined macros. The table + "builtin_tab" is both used for initialization, and by the "builtin" + builtin. */ + +#define DECLARE(name) \ + static void name (struct obstack *, int, token_data **) + +DECLARE (m4___file__); +DECLARE (m4___line__); +DECLARE (m4___program__); +DECLARE (m4_builtin); +DECLARE (m4_changecom); +DECLARE (m4_changequote); +#ifdef ENABLE_CHANGEWORD +DECLARE (m4_changeword); +#endif +DECLARE (m4_debugmode); +DECLARE (m4_debugfile); +DECLARE (m4_decr); +DECLARE (m4_define); +DECLARE (m4_defn); +DECLARE (m4_divert); +DECLARE (m4_divnum); +DECLARE (m4_dnl); +DECLARE (m4_dumpdef); +DECLARE (m4_errprint); +DECLARE (m4_esyscmd); +DECLARE (m4_eval); +DECLARE (m4_format); +DECLARE (m4_ifdef); +DECLARE (m4_ifelse); +DECLARE (m4_include); +DECLARE (m4_incr); +DECLARE (m4_index); +DECLARE (m4_indir); +DECLARE (m4_len); +DECLARE (m4_m4exit); +DECLARE (m4_m4wrap); +DECLARE (m4_maketemp); +DECLARE (m4_mkstemp); +DECLARE (m4_patsubst); +DECLARE (m4_popdef); +DECLARE (m4_pushdef); +DECLARE (m4_regexp); +DECLARE (m4_shift); +DECLARE (m4_sinclude); +DECLARE (m4_substr); +DECLARE (m4_syscmd); +DECLARE (m4_sysval); +DECLARE (m4_traceoff); +DECLARE (m4_traceon); +DECLARE (m4_translit); +DECLARE (m4_undefine); +DECLARE (m4_undivert); + +#undef DECLARE + +static builtin const builtin_tab[] = +{ + + /* name GNUext macros blind function */ + + { "__file__", true, false, false, m4___file__ }, + { "__line__", true, false, false, m4___line__ }, + { "__program__", true, false, false, m4___program__ }, + { "builtin", true, true, true, m4_builtin }, + { "changecom", false, false, false, m4_changecom }, + { "changequote", false, false, false, m4_changequote }, +#ifdef ENABLE_CHANGEWORD + { "changeword", true, false, true, m4_changeword }, +#endif + { "debugmode", true, false, false, m4_debugmode }, + { "debugfile", true, false, false, m4_debugfile }, + { "decr", false, false, true, m4_decr }, + { "define", false, true, true, m4_define }, + { "defn", false, false, true, m4_defn }, + { "divert", false, false, false, m4_divert }, + { "divnum", false, false, false, m4_divnum }, + { "dnl", false, false, false, m4_dnl }, + { "dumpdef", false, false, false, m4_dumpdef }, + { "errprint", false, false, true, m4_errprint }, + { "esyscmd", true, false, true, m4_esyscmd }, + { "eval", false, false, true, m4_eval }, + { "format", true, false, true, m4_format }, + { "ifdef", false, false, true, m4_ifdef }, + { "ifelse", false, false, true, m4_ifelse }, + { "include", false, false, true, m4_include }, + { "incr", false, false, true, m4_incr }, + { "index", false, false, true, m4_index }, + { "indir", true, true, true, m4_indir }, + { "len", false, false, true, m4_len }, + { "m4exit", false, false, false, m4_m4exit }, + { "m4wrap", false, false, true, m4_m4wrap }, + { "maketemp", false, false, true, m4_maketemp }, + { "mkstemp", false, false, true, m4_mkstemp }, + { "patsubst", true, false, true, m4_patsubst }, + { "popdef", false, false, true, m4_popdef }, + { "pushdef", false, true, true, m4_pushdef }, + { "regexp", true, false, true, m4_regexp }, + { "shift", false, false, true, m4_shift }, + { "sinclude", false, false, true, m4_sinclude }, + { "substr", false, false, true, m4_substr }, + { "syscmd", false, false, true, m4_syscmd }, + { "sysval", false, false, false, m4_sysval }, + { "traceoff", false, false, false, m4_traceoff }, + { "traceon", false, false, false, m4_traceon }, + { "translit", false, false, true, m4_translit }, + { "undefine", false, false, true, m4_undefine }, + { "undivert", false, false, false, m4_undivert }, + + { 0, false, false, false, 0 }, + + /* placeholder is intentionally stuck after the table end delimiter, + so that we can easily find it, while not treating it as a real + builtin. */ + { "placeholder", true, false, false, m4_placeholder }, +}; + +static predefined const predefined_tab[] = +{ +#if UNIX + { "unix", "__unix__", "" }, +#endif +#if W32_NATIVE + { "windows", "__windows__", "" }, +#endif +#if OS2 + { "os2", "__os2__", "" }, +#endif +#if !UNIX && !W32_NATIVE && !OS2 +# warning Platform macro not provided +#endif + { NULL, "__gnu__", "" }, + + { NULL, NULL, NULL }, +}; + +/*----------------------------------------. +| Find the builtin, which lives on ADDR. | +`----------------------------------------*/ + +const builtin * M4_GNUC_PURE +find_builtin_by_addr (builtin_func *func) +{ + const builtin *bp; + + for (bp = &builtin_tab[0]; bp->name != NULL; bp++) + if (bp->func == func) + return bp; + if (func == m4_placeholder) + return bp + 1; + return NULL; +} + +/*----------------------------------------------------------. +| Find the builtin, which has NAME. On failure, return the | +| placeholder builtin. | +`----------------------------------------------------------*/ + +const builtin * M4_GNUC_PURE +find_builtin_by_name (const char *name) +{ + const builtin *bp; + + for (bp = &builtin_tab[0]; bp->name != NULL; bp++) + if (STREQ (bp->name, name)) + return bp; + return bp + 1; +} + +/*----------------------------------------------------------------. +| Install a builtin macro with name NAME, bound to the C function | +| given in BP. MODE is SYMBOL_INSERT or SYMBOL_PUSHDEF. | +`----------------------------------------------------------------*/ + +void +define_builtin (const char *name, const builtin *bp, symbol_lookup mode) +{ + symbol *sym; + + sym = lookup_symbol (name, mode); + SYMBOL_TYPE (sym) = TOKEN_FUNC; + SYMBOL_MACRO_ARGS (sym) = bp->groks_macro_args; + SYMBOL_BLIND_NO_ARGS (sym) = bp->blind_if_no_args; + SYMBOL_FUNC (sym) = bp->func; +} + +/* Storage for the compiled regular expression of + --warn-macro-sequence. */ +static struct re_pattern_buffer macro_sequence_buf; + +/* Storage for the matches of --warn-macro-sequence. */ +static struct re_registers macro_sequence_regs; + +/* True if --warn-macro-sequence is in effect. */ +static bool macro_sequence_inuse; + +/*----------------------------------------. +| Clean up regular expression variables. | +`----------------------------------------*/ + +static void +free_pattern_buffer (struct re_pattern_buffer *buf, struct re_registers *regs) +{ + regfree (buf); + free (regs->start); + free (regs->end); +} + +/*-----------------------------------------------------------------. +| Set the regular expression of --warn-macro-sequence that will be | +| checked during define and pushdef. Exit on failure. | +`-----------------------------------------------------------------*/ +void +set_macro_sequence (const char *regexp) +{ + const char *msg; + + if (! regexp) + regexp = DEFAULT_MACRO_SEQUENCE; + else if (regexp[0] == '\0') + { + macro_sequence_inuse = false; + return; + } + + msg = re_compile_pattern (regexp, strlen (regexp), ¯o_sequence_buf); + if (msg != NULL) + { + M4ERROR ((EXIT_FAILURE, 0, + "--warn-macro-sequence: bad regular expression `%s': %s", + regexp, msg)); + } + re_set_registers (¯o_sequence_buf, ¯o_sequence_regs, + macro_sequence_regs.num_regs, + macro_sequence_regs.start, macro_sequence_regs.end); + macro_sequence_inuse = true; +} + +/*-----------------------------------------------------------. +| Free dynamic memory utilized by the macro sequence regular | +| expression during the define builtin. | +`-----------------------------------------------------------*/ +void +free_macro_sequence (void) +{ + free_pattern_buffer (¯o_sequence_buf, ¯o_sequence_regs); +} + +/*-----------------------------------------------------------------. +| Define a predefined or user-defined macro, with name NAME, and | +| expansion TEXT. MODE destinguishes between the "define" and the | +| "pushdef" case. It is also used from main. | +`-----------------------------------------------------------------*/ + +void +define_user_macro (const char *name, const char *text, symbol_lookup mode) +{ + symbol *s; + char *defn = xstrdup (text ? text : ""); + + s = lookup_symbol (name, mode); + if (SYMBOL_TYPE (s) == TOKEN_TEXT) + free (SYMBOL_TEXT (s)); + + SYMBOL_TYPE (s) = TOKEN_TEXT; + SYMBOL_TEXT (s) = defn; + + /* Implement --warn-macro-sequence. */ + if (macro_sequence_inuse && text) + { + regoff_t offset = 0; + size_t len = strlen (defn); + + while ((offset = re_search (¯o_sequence_buf, defn, len, offset, + len - offset, ¯o_sequence_regs)) >= 0) + { + /* Skip empty matches. */ + if (macro_sequence_regs.start[0] == macro_sequence_regs.end[0]) + offset++; + else + { + char tmp; + offset = macro_sequence_regs.end[0]; + tmp = defn[offset]; + defn[offset] = '\0'; + M4ERROR ((warning_status, 0, + "Warning: definition of `%s' contains sequence `%s'", + name, defn + macro_sequence_regs.start[0])); + defn[offset] = tmp; + } + } + if (offset == -2) + M4ERROR ((warning_status, 0, + "error checking --warn-macro-sequence for macro `%s'", + name)); + } +} + +/*-----------------------------------------------. +| Initialize all builtin and predefined macros. | +`-----------------------------------------------*/ + +void +builtin_init (void) +{ + const builtin *bp; + const predefined *pp; + char *string; + + for (bp = &builtin_tab[0]; bp->name != NULL; bp++) + if (!no_gnu_extensions || !bp->gnu_extension) + { + if (prefix_all_builtins) + { + string = (char *) xmalloc (strlen (bp->name) + 4); + strcpy (string, "m4_"); + strcat (string, bp->name); + define_builtin (string, bp, SYMBOL_INSERT); + free (string); + } + else + define_builtin (bp->name, bp, SYMBOL_INSERT); + } + + for (pp = &predefined_tab[0]; pp->func != NULL; pp++) + if (no_gnu_extensions) + { + if (pp->unix_name != NULL) + define_user_macro (pp->unix_name, pp->func, SYMBOL_INSERT); + } + else + { + if (pp->gnu_name != NULL) + define_user_macro (pp->gnu_name, pp->func, SYMBOL_INSERT); + } +} + +/*-------------------------------------------------------------------. +| Give friendly warnings if a builtin macro is passed an | +| inappropriate number of arguments. NAME is the macro name for | +| messages, ARGC is actual number of arguments, MIN is the minimum | +| number of acceptable arguments, negative if not applicable, MAX is | +| the maximum number, negative if not applicable. | +`-------------------------------------------------------------------*/ + +static bool +bad_argc (token_data *name, int argc, int min, int max) +{ + bool isbad = false; + + if (min > 0 && argc < min) + { + if (!suppress_warnings) + M4ERROR ((warning_status, 0, + "Warning: too few arguments to builtin `%s'", + TOKEN_DATA_TEXT (name))); + isbad = true; + } + else if (max > 0 && argc > max && !suppress_warnings) + M4ERROR ((warning_status, 0, + "Warning: excess arguments to builtin `%s' ignored", + TOKEN_DATA_TEXT (name))); + + return isbad; +} + +/*-----------------------------------------------------------------. +| The function numeric_arg () converts ARG to an int pointed to by | +| VALUEP. If the conversion fails, print error message for macro | +| MACRO. Return true iff conversion succeeds. | +`-----------------------------------------------------------------*/ + +static bool +numeric_arg (token_data *macro, const char *arg, int *valuep) +{ + char *endp; + + if (*arg == '\0') + { + *valuep = 0; + M4ERROR ((warning_status, 0, + "empty string treated as 0 in builtin `%s'", + TOKEN_DATA_TEXT (macro))); + } + else + { + errno = 0; + *valuep = strtol (arg, &endp, 10); + if (*endp != '\0') + { + M4ERROR ((warning_status, 0, + "non-numeric argument to builtin `%s'", + TOKEN_DATA_TEXT (macro))); + return false; + } + if (isspace (to_uchar (*arg))) + M4ERROR ((warning_status, 0, + "leading whitespace ignored in builtin `%s'", + TOKEN_DATA_TEXT (macro))); + else if (errno == ERANGE) + M4ERROR ((warning_status, 0, + "numeric overflow detected in builtin `%s'", + TOKEN_DATA_TEXT (macro))); + } + return true; +} + +/*------------------------------------------------------. +| The function ntoa () converts VALUE to a signed ASCII | +| representation in radix RADIX. | +`------------------------------------------------------*/ + +/* Digits for number to ASCII conversions. */ +static char const digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; + +const char * +ntoa (int32_t value, int radix) +{ + bool negative; + uint32_t uvalue; + static char str[256]; + char *s = &str[sizeof str]; + + *--s = '\0'; + + if (value < 0) + { + negative = true; + uvalue = -(uint32_t) value; + } + else + { + negative = false; + uvalue = (uint32_t) value; + } + + do + { + *--s = digits[uvalue % radix]; + uvalue /= radix; + } + while (uvalue > 0); + + if (negative) + *--s = '-'; + return s; +} + +/*---------------------------------------------------------------. +| Format an int VAL, and stuff it into an obstack OBS. Used for | +| macros expanding to numbers. | +`---------------------------------------------------------------*/ + +static void +shipout_int (struct obstack *obs, int val) +{ + const char *s; + + s = ntoa ((int32_t) val, 10); + obstack_grow (obs, s, strlen (s)); +} + +/*-------------------------------------------------------------------. +| Print ARGC arguments from the table ARGV to obstack OBS, separated | +| by SEP, and quoted by the current quotes if QUOTED is true. | +`-------------------------------------------------------------------*/ + +static void +dump_args (struct obstack *obs, int argc, token_data **argv, + const char *sep, bool quoted) +{ + int i; + size_t len = strlen (sep); + + for (i = 1; i < argc; i++) + { + if (i > 1) + obstack_grow (obs, sep, len); + if (quoted) + obstack_grow (obs, lquote.string, lquote.length); + obstack_grow (obs, TOKEN_DATA_TEXT (argv[i]), + strlen (TOKEN_DATA_TEXT (argv[i]))); + if (quoted) + obstack_grow (obs, rquote.string, rquote.length); + } +} + +/* The rest of this file is code for builtins and expansion of user + defined macros. All the functions for builtins have a prototype as: + + void m4_MACRONAME (struct obstack *obs, int argc, char *argv[]); + + The function are expected to leave their expansion on the obstack OBS, + as an unfinished object. ARGV is a table of ARGC pointers to the + individual arguments to the macro. Please note that in general + argv[argc] != NULL. */ + +/* The first section are macros for definining, undefining, examining, + changing, ... other macros. */ + +/*-------------------------------------------------------------------. +| The function define_macro is common for the builtins "define", | +| "undefine", "pushdef" and "popdef". ARGC and ARGV is as for the | +| caller, and MODE argument determines how the macro name is entered | +| into the symbol table. | +`-------------------------------------------------------------------*/ + +static void +define_macro (int argc, token_data **argv, symbol_lookup mode) +{ + const builtin *bp; + + if (bad_argc (argv[0], argc, 2, 3)) + return; + + if (TOKEN_DATA_TYPE (argv[1]) != TOKEN_TEXT) + { + M4ERROR ((warning_status, 0, + "Warning: %s: invalid macro name ignored", ARG (0))); + return; + } + + if (argc == 2) + { + define_user_macro (ARG (1), "", mode); + return; + } + + switch (TOKEN_DATA_TYPE (argv[2])) + { + case TOKEN_TEXT: + define_user_macro (ARG (1), ARG (2), mode); + break; + + case TOKEN_FUNC: + bp = find_builtin_by_addr (TOKEN_DATA_FUNC (argv[2])); + if (bp == NULL) + return; + else + define_builtin (ARG (1), bp, mode); + break; + + case TOKEN_VOID: + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: bad token data type in define_macro ()")); + abort (); + } +} + +static void +m4_define (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + define_macro (argc, argv, SYMBOL_INSERT); +} + +static void +m4_undefine (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + int i; + if (bad_argc (argv[0], argc, 2, -1)) + return; + for (i = 1; i < argc; i++) + lookup_symbol (ARG (i), SYMBOL_DELETE); +} + +static void +m4_pushdef (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + define_macro (argc, argv, SYMBOL_PUSHDEF); +} + +static void +m4_popdef (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + int i; + if (bad_argc (argv[0], argc, 2, -1)) + return; + for (i = 1; i < argc; i++) + lookup_symbol (ARG (i), SYMBOL_POPDEF); +} + +/*---------------------. +| Conditionals of m4. | +`---------------------*/ + +static void +m4_ifdef (struct obstack *obs, int argc, token_data **argv) +{ + symbol *s; + const char *result; + + if (bad_argc (argv[0], argc, 3, 4)) + return; + s = lookup_symbol (ARG (1), SYMBOL_LOOKUP); + + if (s != NULL && SYMBOL_TYPE (s) != TOKEN_VOID) + result = ARG (2); + else if (argc >= 4) + result = ARG (3); + else + result = NULL; + + if (result != NULL) + obstack_grow (obs, result, strlen (result)); +} + +static void +m4_ifelse (struct obstack *obs, int argc, token_data **argv) +{ + const char *result; + token_data *me = argv[0]; + + if (argc == 2) + return; + + if (bad_argc (me, argc, 4, -1)) + return; + else + /* Diagnose excess arguments if 5, 8, 11, etc., actual arguments. */ + bad_argc (me, (argc + 2) % 3, -1, 1); + + argv++; + argc--; + + result = NULL; + while (result == NULL) + + if (STREQ (ARG (0), ARG (1))) + result = ARG (2); + + else + switch (argc) + { + case 3: + return; + + case 4: + case 5: + result = ARG (3); + break; + + default: + argc -= 3; + argv += 3; + } + + obstack_grow (obs, result, strlen (result)); +} + +/*-------------------------------------------------------------------. +| The function dump_symbol () is for use by "dumpdef". It builds up | +| a table of all defined, un-shadowed, symbols. | +`-------------------------------------------------------------------*/ + +/* The structure dump_symbol_data is used to pass the information needed + from call to call to dump_symbol. */ + +struct dump_symbol_data +{ + struct obstack *obs; /* obstack for table */ + symbol **base; /* base of table */ + int size; /* size of table */ +}; + +static void +dump_symbol (symbol *sym, void *arg) +{ + struct dump_symbol_data *data = (struct dump_symbol_data *) arg; + if (!SYMBOL_SHADOWED (sym) && SYMBOL_TYPE (sym) != TOKEN_VOID) + { + obstack_blank (data->obs, sizeof (symbol *)); + data->base = (symbol **) obstack_base (data->obs); + data->base[data->size++] = sym; + } +} + +/*------------------------------------------------------------------------. +| qsort comparison routine, for sorting the table made in m4_dumpdef (). | +`------------------------------------------------------------------------*/ + +static int +dumpdef_cmp (const void *s1, const void *s2) +{ + return strcmp (SYMBOL_NAME (* (symbol *const *) s1), + SYMBOL_NAME (* (symbol *const *) s2)); +} + +/*-------------------------------------------------------------. +| Implementation of "dumpdef" itself. It builds up a table of | +| pointers to symbols, sorts it and prints the sorted table. | +`-------------------------------------------------------------*/ + +static void +m4_dumpdef (struct obstack *obs, int argc, token_data **argv) +{ + symbol *s; + int i; + struct dump_symbol_data data; + const builtin *bp; + + data.obs = obs; + data.base = (symbol **) obstack_base (obs); + data.size = 0; + + if (argc == 1) + { + hack_all_symbols (dump_symbol, &data); + } + else + { + for (i = 1; i < argc; i++) + { + s = lookup_symbol (TOKEN_DATA_TEXT (argv[i]), SYMBOL_LOOKUP); + if (s != NULL && SYMBOL_TYPE (s) != TOKEN_VOID) + dump_symbol (s, &data); + else + M4ERROR ((warning_status, 0, + "undefined macro `%s'", TOKEN_DATA_TEXT (argv[i]))); + } + } + + /* Make table of symbols invisible to expand_macro (). */ + + obstack_finish (obs); + + qsort (data.base, data.size, sizeof (symbol *), dumpdef_cmp); + + for (; data.size > 0; --data.size, data.base++) + { + DEBUG_PRINT1 ("%s:\t", SYMBOL_NAME (data.base[0])); + + switch (SYMBOL_TYPE (data.base[0])) + { + case TOKEN_TEXT: + if (debug_level & DEBUG_TRACE_QUOTE) + DEBUG_PRINT3 ("%s%s%s\n", + lquote.string, SYMBOL_TEXT (data.base[0]), rquote.string); + else + DEBUG_PRINT1 ("%s\n", SYMBOL_TEXT (data.base[0])); + break; + + case TOKEN_FUNC: + bp = find_builtin_by_addr (SYMBOL_FUNC (data.base[0])); + if (bp == NULL) + { + M4ERROR ((warning_status, 0, "\ +INTERNAL ERROR: builtin not found in builtin table")); + abort (); + } + DEBUG_PRINT1 ("<%s>\n", bp->name); + break; + + case TOKEN_VOID: + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: bad token data type in m4_dumpdef ()")); + abort (); + break; + } + } +} + +/*-----------------------------------------------------------------. +| The builtin "builtin" allows calls to builtin macros, even if | +| their definition has been overridden or shadowed. It is thus | +| possible to redefine builtins, and still access their original | +| definition. This macro is not available in compatibility mode. | +`-----------------------------------------------------------------*/ + +static void +m4_builtin (struct obstack *obs, int argc, token_data **argv) +{ + const builtin *bp; + const char *name; + + if (bad_argc (argv[0], argc, 2, -1)) + return; + if (TOKEN_DATA_TYPE (argv[1]) != TOKEN_TEXT) + { + M4ERROR ((warning_status, 0, + "Warning: %s: invalid macro name ignored", ARG (0))); + return; + } + + name = ARG (1); + bp = find_builtin_by_name (name); + if (bp->func == m4_placeholder) + M4ERROR ((warning_status, 0, + "undefined builtin `%s'", name)); + else + { + int i; + if (! bp->groks_macro_args) + for (i = 2; i < argc; i++) + if (TOKEN_DATA_TYPE (argv[i]) != TOKEN_TEXT) + { + TOKEN_DATA_TYPE (argv[i]) = TOKEN_TEXT; + TOKEN_DATA_TEXT (argv[i]) = (char *) ""; + } + bp->func (obs, argc - 1, argv + 1); + } +} + +/*-------------------------------------------------------------------. +| The builtin "indir" allows indirect calls to macros, even if their | +| name is not a proper macro name. It is thus possible to define | +| macros with ill-formed names for internal use in larger macro | +| packages. This macro is not available in compatibility mode. | +`-------------------------------------------------------------------*/ + +static void +m4_indir (struct obstack *obs, int argc, token_data **argv) +{ + symbol *s; + const char *name; + + if (bad_argc (argv[0], argc, 2, -1)) + return; + if (TOKEN_DATA_TYPE (argv[1]) != TOKEN_TEXT) + { + M4ERROR ((warning_status, 0, + "Warning: %s: invalid macro name ignored", ARG (0))); + return; + } + + name = ARG (1); + s = lookup_symbol (name, SYMBOL_LOOKUP); + if (s == NULL || SYMBOL_TYPE (s) == TOKEN_VOID) + M4ERROR ((warning_status, 0, + "undefined macro `%s'", name)); + else + { + int i; + if (! SYMBOL_MACRO_ARGS (s)) + for (i = 2; i < argc; i++) + if (TOKEN_DATA_TYPE (argv[i]) != TOKEN_TEXT) + { + TOKEN_DATA_TYPE (argv[i]) = TOKEN_TEXT; + TOKEN_DATA_TEXT (argv[i]) = (char *) ""; + } + call_macro (s, argc - 1, argv + 1, obs); + } +} + +/*------------------------------------------------------------------. +| The macro "defn" returns the quoted definition of the macro named | +| by the first argument. If the macro is builtin, it will push a | +| special macro-definition token on the input stack. | +`------------------------------------------------------------------*/ + +static void +m4_defn (struct obstack *obs, int argc, token_data **argv) +{ + symbol *s; + builtin_func *b; + unsigned int i; + + if (bad_argc (argv[0], argc, 2, -1)) + return; + + assert (0 < argc); + for (i = 1; i < (unsigned) argc; i++) + { + const char *arg = ARG((int) i); + s = lookup_symbol (arg, SYMBOL_LOOKUP); + if (s == NULL) + continue; + + switch (SYMBOL_TYPE (s)) + { + case TOKEN_TEXT: + obstack_grow (obs, lquote.string, lquote.length); + obstack_grow (obs, SYMBOL_TEXT (s), strlen (SYMBOL_TEXT (s))); + obstack_grow (obs, rquote.string, rquote.length); + break; + + case TOKEN_FUNC: + b = SYMBOL_FUNC (s); + if (b == m4_placeholder) + M4ERROR ((warning_status, 0, "\ +builtin `%s' requested by frozen file is not supported", arg)); + else if (argc != 2) + M4ERROR ((warning_status, 0, + "Warning: cannot concatenate builtin `%s'", + arg)); + else + push_macro (b); + break; + + case TOKEN_VOID: + /* Nothing to do for traced but undefined macro. */ + break; + + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: bad symbol type in m4_defn ()")); + abort (); + } + } +} + +/*--------------------------------------------------------------. +| This section contains macros to handle the builtins "syscmd", | +| "esyscmd" and "sysval". "esyscmd" is GNU specific. | +`--------------------------------------------------------------*/ + +/* Exit code from last "syscmd" command. */ +static int sysval; + +static void +m4_syscmd (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + const char *cmd = ARG (1); + int status; + int sig_status; + const char *prog_args[4] = { "sh", "-c" }; + if (bad_argc (argv[0], argc, 2, 2) || !*cmd) + { + /* The empty command is successful. */ + sysval = 0; + return; + } + + debug_flush_files (); +#if W32_NATIVE + if (strstr (SYSCMD_SHELL, "cmd")) + { + prog_args[0] = "cmd"; + prog_args[1] = "/c"; + } +#endif + prog_args[2] = cmd; + errno = 0; + status = execute (ARG (0), SYSCMD_SHELL, (char **) prog_args, false, + false, false, false, true, false, &sig_status); + if (sig_status) + { + assert (status == 127); + sysval = sig_status << 8; + } + else + { + if (status == 127 && errno) + M4ERROR ((warning_status, errno, "cannot run command `%s'", cmd)); + sysval = status; + } +} + +static void +m4_esyscmd (struct obstack *obs, int argc, token_data **argv) +{ + const char *cmd = ARG (1); + const char *prog_args[4] = { "sh", "-c" }; + pid_t child; + int fd; + FILE *pin; + int status; + int sig_status; + + if (bad_argc (argv[0], argc, 2, 2) || !*cmd) + { + /* The empty command is successful. */ + sysval = 0; + return; + } + + debug_flush_files (); +#if W32_NATIVE + if (strstr (SYSCMD_SHELL, "cmd")) + { + prog_args[0] = "cmd"; + prog_args[1] = "/c"; + } +#endif + prog_args[2] = cmd; + errno = 0; + child = create_pipe_in (ARG (0), SYSCMD_SHELL, (char **) prog_args, + NULL, false, true, false, &fd); + if (child == -1) + { + M4ERROR ((warning_status, errno, "cannot run command `%s'", cmd)); + sysval = 127; + return; + } + pin = fdopen (fd, "r"); + if (pin == NULL) + { + M4ERROR ((warning_status, errno, "cannot run command `%s'", cmd)); + sysval = 127; + close (fd); + return; + } + while (1) + { + size_t avail = obstack_room (obs); + size_t len; + if (!avail) + { + int ch = getc (pin); + if (ch == EOF) + break; + obstack_1grow (obs, ch); + continue; + } + len = fread (obstack_next_free (obs), 1, avail, pin); + if (len <= 0) + break; + obstack_blank_fast (obs, len); + } + if (ferror (pin) || fclose (pin)) + M4ERROR ((EXIT_FAILURE, errno, "cannot read pipe")); + errno = 0; + status = wait_subprocess (child, ARG (0), false, true, true, false, + &sig_status); + if (sig_status) + { + assert (status == 127); + sysval = sig_status << 8; + } + else + { + if (status == 127 && errno) + M4ERROR ((warning_status, errno, "cannot run command `%s'", cmd)); + sysval = status; + } +} + +static void +m4_sysval (struct obstack *obs, int argc M4_GNUC_UNUSED, + token_data **argv M4_GNUC_UNUSED) +{ + shipout_int (obs, sysval); +} + +/*------------------------------------------------------------------. +| This section contains the top level code for the "eval" builtin. | +| The actual work is done in the function evaluate (), which lives | +| in eval.c. | +`------------------------------------------------------------------*/ + +static void +m4_eval (struct obstack *obs, int argc, token_data **argv) +{ + int32_t value = 0; + int radix = 10; + int min = 1; + const char *s; + + if (bad_argc (argv[0], argc, 2, 4)) + return; + + if (*ARG (2) && !numeric_arg (argv[0], ARG (2), &radix)) + return; + + if (radix < 1 || radix > (int) strlen (digits)) + { + M4ERROR ((warning_status, 0, + "radix %d in builtin `%s' out of range", + radix, ARG (0))); + return; + } + + if (argc >= 4 && !numeric_arg (argv[0], ARG (3), &min)) + return; + if (min < 0) + { + M4ERROR ((warning_status, 0, + "negative width to builtin `%s'", ARG (0))); + return; + } + + if (!*ARG (1)) + M4ERROR ((warning_status, 0, + "empty string treated as 0 in builtin `%s'", ARG (0))); + else if (evaluate (ARG (1), &value)) + return; + + if (radix == 1) + { + if (value < 0) + { + obstack_1grow (obs, '-'); + value = -value; + } + /* This assumes 2's-complement for correctly handling INT_MIN. */ + while (min-- - value > 0) + obstack_1grow (obs, '0'); + while (value-- != 0) + obstack_1grow (obs, '1'); + obstack_1grow (obs, '\0'); + return; + } + + s = ntoa (value, radix); + + if (*s == '-') + { + obstack_1grow (obs, '-'); + s++; + } + for (min -= strlen (s); --min >= 0;) + obstack_1grow (obs, '0'); + + obstack_grow (obs, s, strlen (s)); +} + +static void +m4_incr (struct obstack *obs, int argc, token_data **argv) +{ + int value; + + if (bad_argc (argv[0], argc, 2, 2)) + return; + + if (!numeric_arg (argv[0], ARG (1), &value)) + return; + + shipout_int (obs, value + 1); +} + +static void +m4_decr (struct obstack *obs, int argc, token_data **argv) +{ + int value; + + if (bad_argc (argv[0], argc, 2, 2)) + return; + + if (!numeric_arg (argv[0], ARG (1), &value)) + return; + + shipout_int (obs, value - 1); +} + +/* This section contains the macros "divert", "undivert" and "divnum" for + handling diversion. The utility functions used lives in output.c. */ + +/*-----------------------------------------------------------------. +| Divert further output to the diversion given by ARGV[1]. Out of | +| range means discard further output. | +`-----------------------------------------------------------------*/ + +static void +m4_divert (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + int i = 0; + + if (bad_argc (argv[0], argc, 1, 2)) + return; + + if (argc >= 2 && !numeric_arg (argv[0], ARG (1), &i)) + return; + + make_diversion (i); +} + +/*-----------------------------------------------------. +| Expand to the current diversion number, -1 if none. | +`-----------------------------------------------------*/ + +static void +m4_divnum (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 1, 1)) + return; + shipout_int (obs, current_diversion); +} + +/*------------------------------------------------------------------. +| Bring back the diversion given by the argument list. If none is | +| specified, bring back all diversions. GNU specific is the option | +| of undiverting named files, by passing a non-numeric argument to | +| undivert (). | +`------------------------------------------------------------------*/ + +static void +m4_undivert (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + int i, file; + FILE *fp; + char *endp; + + if (argc == 1) + undivert_all (); + else + for (i = 1; i < argc; i++) + { + file = strtol (ARG (i), &endp, 10); + if (*endp == '\0' && !isspace (to_uchar (*ARG (i)))) + insert_diversion (file); + else if (no_gnu_extensions) + M4ERROR ((warning_status, 0, + "non-numeric argument to builtin `%s'", ARG (0))); + else + { + fp = m4_path_search (ARG (i), NULL); + if (fp != NULL) + { + insert_file (fp); + if (fclose (fp) == EOF) + M4ERROR ((warning_status, errno, + "error undiverting `%s'", ARG (i))); + } + else + M4ERROR ((warning_status, errno, + "cannot undivert `%s'", ARG (i))); + } + } +} + +/* This section contains various macros, which does not fall into any + specific group. These are "dnl", "shift", "changequote", "changecom" + and "changeword". */ + +/*-----------------------------------------------------------. +| Delete all subsequent whitespace from input. The function | +| skip_line () lives in input.c. | +`-----------------------------------------------------------*/ + +static void +m4_dnl (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 1, 1)) + return; + + skip_line (); +} + +/*--------------------------------------------------------------------. +| Shift all arguments one to the left, discarding the first | +| argument. Each output argument is quoted with the current quotes. | +`--------------------------------------------------------------------*/ + +static void +m4_shift (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 2, -1)) + return; + dump_args (obs, argc - 1, argv + 1, ",", true); +} + +/*--------------------------------------------------------------------------. +| Change the current quotes. The function set_quotes () lives in input.c. | +`--------------------------------------------------------------------------*/ + +static void +m4_changequote (struct obstack *obs M4_GNUC_UNUSED, int argc, + token_data **argv) +{ + if (bad_argc (argv[0], argc, 1, 3)) + return; + + /* Explicit NULL distinguishes between empty and missing argument. */ + set_quotes ((argc >= 2) ? TOKEN_DATA_TEXT (argv[1]) : NULL, + (argc >= 3) ? TOKEN_DATA_TEXT (argv[2]) : NULL); +} + +/*-----------------------------------------------------------------. +| Change the current comment delimiters. The function set_comment | +| () lives in input.c. | +`-----------------------------------------------------------------*/ + +static void +m4_changecom (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 1, 3)) + return; + + /* Explicit NULL distinguishes between empty and missing argument. */ + set_comment ((argc >= 2) ? TOKEN_DATA_TEXT (argv[1]) : NULL, + (argc >= 3) ? TOKEN_DATA_TEXT (argv[2]) : NULL); +} + +#ifdef ENABLE_CHANGEWORD + +/*---------------------------------------------------------------. +| Change the regular expression used for breaking the input into | +| words. The function set_word_regexp () lives in input.c. | +`---------------------------------------------------------------*/ + +static void +m4_changeword (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 2, 2)) + return; + + set_word_regexp (TOKEN_DATA_TEXT (argv[1])); +} + +#endif /* ENABLE_CHANGEWORD */ + +/* This section contains macros for inclusion of other files -- "include" + and "sinclude". This differs from bringing back diversions, in that + the input is scanned before being copied to the output. */ + +/*---------------------------------------------------------------. +| Generic include function. Include the file given by the first | +| argument, if it exists. Complain about inaccessible files iff | +| SILENT is false. | +`---------------------------------------------------------------*/ + +static void +include (int argc, token_data **argv, bool silent) +{ + FILE *fp; + char *name; + + if (bad_argc (argv[0], argc, 2, 2)) + return; + + fp = m4_path_search (ARG (1), &name); + if (fp == NULL) + { + if (!silent) + { + M4ERROR ((warning_status, errno, "cannot open `%s'", ARG (1))); + retcode = EXIT_FAILURE; + } + return; + } + + push_file (fp, name, true); + free (name); +} + +/*------------------------------------------------. +| Include a file, complaining in case of errors. | +`------------------------------------------------*/ + +static void +m4_include (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + include (argc, argv, false); +} + +/*----------------------------------. +| Include a file, ignoring errors. | +`----------------------------------*/ + +static void +m4_sinclude (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + include (argc, argv, true); +} + +/* More miscellaneous builtins -- "maketemp", "errprint", "__file__", + "__line__", and "__program__". The last three are GNU specific. */ + +/*------------------------------------------------------------------. +| Use the first argument as at template for a temporary file name. | +`------------------------------------------------------------------*/ + +/* Add trailing 'X' to PATTERN of length LEN as necessary, then + securely create the file, and place the quoted new file name on + OBS. Report errors on behalf of ME. */ +static void +mkstemp_helper (struct obstack *obs, const char *me, const char *pattern, + size_t len) +{ + int fd; + size_t i; + char *name; + + /* Guarantee that there are six trailing 'X' characters, even if the + user forgot to supply them. Output must be quoted if + successful. */ + obstack_grow (obs, lquote.string, lquote.length); + obstack_grow (obs, pattern, len); + for (i = 0; len > 0 && i < 6; i++) + if (pattern[len - i - 1] != 'X') + break; + obstack_grow0 (obs, "XXXXXX", 6 - i); + name = (char *) obstack_base (obs) + lquote.length; + + errno = 0; + fd = mkstemp (name); + if (fd < 0) + { + M4ERROR ((0, errno, "%s: cannot create tempfile `%s'", me, pattern)); + obstack_free (obs, obstack_finish (obs)); + } + else + { + close (fd); + /* Remove NUL, then finish quote. */ + obstack_blank (obs, -1); + obstack_grow (obs, rquote.string, rquote.length); + } +} + +static void +m4_maketemp (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 2, 2)) + return; + if (no_gnu_extensions) + { + /* POSIX states "any trailing 'X' characters [are] replaced with + the current process ID as a string", without referencing the + file system. Horribly insecure, but we have to do it when we + are in traditional mode. + + For reference, Solaris m4 does: + maketemp() -> `' + maketemp(X) -> `X' + maketemp(XX) -> `Xn', where n is last digit of pid + maketemp(XXXXXXXX) -> `X00nnnnn', where nnnnn is 16-bit pid + */ + const char *str = ARG (1); + int len = strlen (str); + int i; + int len2; + + M4ERROR ((warning_status, 0, "recommend using mkstemp instead")); + for (i = len; i > 1; i--) + if (str[i - 1] != 'X') + break; + obstack_grow (obs, str, i); + str = ntoa ((int32_t) getpid (), 10); + len2 = strlen (str); + if (len2 > len - i) + obstack_grow0 (obs, str + len2 - (len - i), len - i); + else + { + while (i++ < len - len2) + obstack_1grow (obs, '0'); + obstack_grow0 (obs, str, len2); + } + } + else + mkstemp_helper (obs, ARG (0), ARG (1), strlen (ARG (1))); +} + +static void +m4_mkstemp (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 2, 2)) + return; + mkstemp_helper (obs, ARG (0), ARG (1), strlen (ARG (1))); +} + +/*----------------------------------------. +| Print all arguments on standard error. | +`----------------------------------------*/ + +static void +m4_errprint (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 2, -1)) + return; + dump_args (obs, argc, argv, " ", false); + obstack_1grow (obs, '\0'); + debug_flush_files (); + xfprintf (stderr, "%s", (char *) obstack_finish (obs)); + fflush (stderr); +} + +static void +m4___file__ (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 1, 1)) + return; + obstack_grow (obs, lquote.string, lquote.length); + obstack_grow (obs, current_file, strlen (current_file)); + obstack_grow (obs, rquote.string, rquote.length); +} + +static void +m4___line__ (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 1, 1)) + return; + shipout_int (obs, current_line); +} + +static void +m4___program__ (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 1, 1)) + return; + obstack_grow (obs, lquote.string, lquote.length); + obstack_grow (obs, program_name, strlen (program_name)); + obstack_grow (obs, rquote.string, rquote.length); +} + +/* This section contains various macros for exiting, saving input until + EOF is seen, and tracing macro calls. That is: "m4exit", "m4wrap", + "traceon" and "traceoff". */ + +/*----------------------------------------------------------. +| Exit immediately, with exit status specified by the first | +| argument, or 0 if no arguments are present. | +`----------------------------------------------------------*/ + +static void M4_GNUC_NORETURN +m4_m4exit (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + int exit_code = EXIT_SUCCESS; + + /* Warn on bad arguments, but still exit. */ + bad_argc (argv[0], argc, 1, 2); + if (argc >= 2 && !numeric_arg (argv[0], ARG (1), &exit_code)) + exit_code = EXIT_FAILURE; + if (exit_code < 0 || exit_code > 255) + { + M4ERROR ((warning_status, 0, + "exit status out of range: `%d'", exit_code)); + exit_code = EXIT_FAILURE; + } + /* Change debug stream back to stderr, to force flushing debug stream and + detect any errors it might have encountered. */ + debug_set_output (NULL); + debug_flush_files (); + if (exit_code == EXIT_SUCCESS && retcode != EXIT_SUCCESS) + exit_code = retcode; + /* Propagate non-zero status to atexit handlers. */ + if (exit_code != EXIT_SUCCESS) + exit_failure = exit_code; + exit (exit_code); +} + +/*------------------------------------------------------------------. +| Save the argument text until EOF has been seen, allowing for user | +| specified cleanup action. GNU version saves all arguments, the | +| standard version only the first. | +`------------------------------------------------------------------*/ + +static void +m4_m4wrap (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 2, -1)) + return; + if (no_gnu_extensions) + obstack_grow (obs, ARG (1), strlen (ARG (1))); + else + dump_args (obs, argc, argv, " ", false); + obstack_1grow (obs, '\0'); + push_wrapup ((char *) obstack_finish (obs)); +} + +/* Enable tracing of all specified macros, or all, if none is specified. + Tracing is disabled by default, when a macro is defined. This can be + overridden by the "t" debug flag. */ + +/*------------------------------------------------------------------. +| Set_trace () is used by "traceon" and "traceoff" to enable and | +| disable tracing of a macro. It disables tracing if DATA is NULL, | +| otherwise it enables tracing. | +`------------------------------------------------------------------*/ + +static void +set_trace (symbol *sym, void *data) +{ + SYMBOL_TRACED (sym) = data != NULL; + /* Remove placeholder from table if macro is undefined and untraced. */ + if (SYMBOL_TYPE (sym) == TOKEN_VOID && data == NULL) + lookup_symbol (SYMBOL_NAME (sym), SYMBOL_POPDEF); +} + +static void +m4_traceon (struct obstack *obs, int argc, token_data **argv) +{ + symbol *s; + int i; + + if (argc == 1) + hack_all_symbols (set_trace, obs); + else + for (i = 1; i < argc; i++) + { + s = lookup_symbol (ARG (i), SYMBOL_LOOKUP); + if (!s) + s = lookup_symbol (ARG (i), SYMBOL_INSERT); + set_trace (s, obs); + } +} + +/*------------------------------------------------------------------------. +| Disable tracing of all specified macros, or all, if none is specified. | +`------------------------------------------------------------------------*/ + +static void +m4_traceoff (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + symbol *s; + int i; + + if (argc == 1) + hack_all_symbols (set_trace, NULL); + else + for (i = 1; i < argc; i++) + { + s = lookup_symbol (TOKEN_DATA_TEXT (argv[i]), SYMBOL_LOOKUP); + if (s != NULL) + set_trace (s, NULL); + } +} + +/*------------------------------------------------------------------. +| On-the-fly control of the format of the tracing output. It takes | +| one argument, which is a character string like given to the -d | +| option, or none in which case the debug_level is zeroed. | +`------------------------------------------------------------------*/ + +static void +m4_debugmode (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + int new_debug_level; + int change_flag; + + if (bad_argc (argv[0], argc, 1, 2)) + return; + + if (argc == 1) + debug_level = 0; + else + { + if (ARG (1)[0] == '+' || ARG (1)[0] == '-') + { + change_flag = ARG (1)[0]; + new_debug_level = debug_decode (ARG (1) + 1); + } + else + { + change_flag = 0; + new_debug_level = debug_decode (ARG (1)); + } + + if (new_debug_level < 0) + M4ERROR ((warning_status, 0, + "Debugmode: bad debug flags: `%s'", ARG (1))); + else + { + switch (change_flag) + { + case 0: + debug_level = new_debug_level; + break; + + case '+': + debug_level |= new_debug_level; + break; + + case '-': + debug_level &= ~new_debug_level; + break; + + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: bad flag in m4_debugmode ()")); + abort (); + } + } + } +} + +/*-------------------------------------------------------------------------. +| Specify the destination of the debugging output. With one argument, the | +| argument is taken as a file name, with no arguments, revert to stderr. | +`-------------------------------------------------------------------------*/ + +static void +m4_debugfile (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 1, 2)) + return; + + if (argc == 1) + debug_set_output (NULL); + else if (!debug_set_output (ARG (1))) + M4ERROR ((warning_status, errno, + "cannot set debug file `%s'", ARG (1))); +} + +/* This section contains text processing macros: "len", "index", + "substr", "translit", "format", "regexp" and "patsubst". The last + three are GNU specific. */ + +/*---------------------------------------------. +| Expand to the length of the first argument. | +`---------------------------------------------*/ + +static void +m4_len (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 2, 2)) + return; + shipout_int (obs, strlen (ARG (1))); +} + +/*-------------------------------------------------------------------. +| The macro expands to the first index of the second argument in the | +| first argument. | +`-------------------------------------------------------------------*/ + +static void +m4_index (struct obstack *obs, int argc, token_data **argv) +{ + const char *haystack; + const char *result; + int retval; + + if (bad_argc (argv[0], argc, 3, 3)) + { + /* builtin(`index') is blank, but index(`abc') is 0. */ + if (argc == 2) + shipout_int (obs, 0); + return; + } + + haystack = ARG (1); + result = strstr (haystack, ARG (2)); + retval = result ? result - haystack : -1; + + shipout_int (obs, retval); +} + +/*-----------------------------------------------------------------. +| The macro "substr" extracts substrings from the first argument, | +| starting from the index given by the second argument, extending | +| for a length given by the third argument. If the third argument | +| is missing, the substring extends to the end of the first | +| argument. | +`-----------------------------------------------------------------*/ + +static void +m4_substr (struct obstack *obs, int argc, token_data **argv) +{ + int start = 0; + int length, avail; + + if (bad_argc (argv[0], argc, 3, 4)) + { + /* builtin(`substr') is blank, but substr(`abc') is abc. */ + if (argc == 2) + obstack_grow (obs, ARG (1), strlen (ARG (1))); + return; + } + + length = avail = strlen (ARG (1)); + if (!numeric_arg (argv[0], ARG (2), &start)) + return; + + if (argc >= 4 && !numeric_arg (argv[0], ARG (3), &length)) + return; + + if (start < 0 || length <= 0 || start >= avail) + return; + + if (start + length > avail) + length = avail - start; + obstack_grow (obs, ARG (1) + start, length); +} + +/*------------------------------------------------------------------. +| For "translit", ranges are allowed in the second and third | +| argument. They are expanded in the following function, and the | +| expanded strings, without any ranges left, are used to translate | +| the characters of the first argument. A single - (dash) can be | +| included in the strings by being the first or the last character | +| in the string. If the first character in a range is after the | +| first in the character set, the range is made backwards, thus 9-0 | +| is the string 9876543210. | +`------------------------------------------------------------------*/ + +static const char * +expand_ranges (const char *s, struct obstack *obs) +{ + unsigned char from; + unsigned char to; + + for (from = '\0'; *s != '\0'; from = to_uchar (*s++)) + { + if (*s == '-' && from != '\0') + { + to = to_uchar (*++s); + if (to == '\0') + { + /* trailing dash */ + obstack_1grow (obs, '-'); + break; + } + else if (from <= to) + { + while (from++ < to) + obstack_1grow (obs, from); + } + else + { + while (--from >= to) + obstack_1grow (obs, from); + } + } + else + obstack_1grow (obs, *s); + } + obstack_1grow (obs, '\0'); + return (char *) obstack_finish (obs); +} + +/*-----------------------------------------------------------------. +| The macro "translit" translates all characters in the first | +| argument, which are present in the second argument, into the | +| corresponding character from the third argument. If the third | +| argument is shorter than the second, the extra characters in the | +| second argument are deleted from the first. | +`-----------------------------------------------------------------*/ + +static void +m4_translit (struct obstack *obs, int argc, token_data **argv) +{ + const char *data = ARG (1); + const char *from = ARG (2); + const char *to; + char map[UCHAR_MAX + 1]; + char found[UCHAR_MAX + 1]; + unsigned char ch; + + if (bad_argc (argv[0], argc, 3, 4) || !*data || !*from) + { + /* builtin(`translit') is blank, but translit(`abc') is abc. */ + if (2 <= argc) + obstack_grow (obs, data, strlen (data)); + return; + } + + to = ARG (3); + if (strchr (to, '-') != NULL) + { + to = expand_ranges (to, obs); + assert (to && *to); + } + + /* If there are only one or two bytes to replace, it is faster to + use memchr2. Using expand_ranges does nothing unless there are + at least three bytes. */ + if (!from[1] || !from[2]) + { + const char *p; + size_t len = strlen (data); + while ((p = (char *) memchr2 (data, from[0], from[1], len))) + { + obstack_grow (obs, data, p - data); + len -= p - data; + if (!len) + return; + data = p + 1; + len--; + if (*p == from[0] && to[0]) + obstack_1grow (obs, to[0]); + else if (*p == from[1] && to[0] && to[1]) + obstack_1grow (obs, to[1]); + } + obstack_grow (obs, data, len); + return; + } + + if (strchr (from, '-') != NULL) + { + from = expand_ranges (from, obs); + assert (from && *from); + } + + /* Calling strchr(from) for each character in data is quadratic, + since both strings can be arbitrarily long. Instead, create a + from-to mapping in one pass of from, then use that map in one + pass of data, for linear behavior. Traditional behavior is that + only the first instance of a character in from is consulted, + hence the found map. */ + memset (map, 0, sizeof map); + memset (found, 0, sizeof found); + for ( ; (ch = *from) != '\0'; from++) + { + if (! found[ch]) + { + found[ch] = 1; + map[ch] = *to; + } + if (*to != '\0') + to++; + } + + for (data = ARG (1); (ch = *data) != '\0'; data++) + { + if (! found[ch]) + obstack_1grow (obs, ch); + else if (map[ch]) + obstack_1grow (obs, map[ch]); + } +} + +/*-------------------------------------------------------------------. +| Frontend for printf like formatting. The function format () lives | +| in the file format.c. | +`-------------------------------------------------------------------*/ + +static void +m4_format (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 2, -1)) + return; + expand_format (obs, argc - 1, argv + 1); +} + +/*------------------------------------------------------------------. +| Function to perform substitution by regular expressions. Used by | +| the builtins regexp and patsubst. The changed text is placed on | +| the obstack. The substitution is REPL, with \& substituted by | +| this part of VICTIM matched by the last whole regular expression, | +| taken from REGS[0], and \N substituted by the text matched by the | +| Nth parenthesized sub-expression, taken from REGS[N]. | +`------------------------------------------------------------------*/ + +static int substitute_warned = 0; + +static void +substitute (struct obstack *obs, const char *victim, const char *repl, + struct re_registers *regs) +{ + int ch; + __re_size_t ind; + while (1) + { + const char *backslash = strchr (repl, '\\'); + if (!backslash) + { + obstack_grow (obs, repl, strlen (repl)); + return; + } + obstack_grow (obs, repl, backslash - repl); + repl = backslash; + ch = *++repl; + switch (ch) + { + case '0': + if (!substitute_warned) + { + M4ERROR ((warning_status, 0, "\ +Warning: \\0 will disappear, use \\& instead in replacements")); + substitute_warned = 1; + } + /* Fall through. */ + + case '&': + obstack_grow (obs, victim + regs->start[0], + regs->end[0] - regs->start[0]); + repl++; + break; + + case '1': case '2': case '3': case '4': case '5': case '6': + case '7': case '8': case '9': + ind = ch -= '0'; + if (regs->num_regs - 1 <= ind) + M4ERROR ((warning_status, 0, + "Warning: sub-expression %d not present", ch)); + else if (regs->end[ch] > 0) + obstack_grow (obs, victim + regs->start[ch], + regs->end[ch] - regs->start[ch]); + repl++; + break; + + case '\0': + M4ERROR ((warning_status, 0, + "Warning: trailing \\ ignored in replacement")); + return; + + default: + obstack_1grow (obs, ch); + repl++; + break; + } + } +} + +/*------------------------------------------. +| Initialize regular expression variables. | +`------------------------------------------*/ + +void +init_pattern_buffer (struct re_pattern_buffer *buf, struct re_registers *regs) +{ + buf->translate = NULL; + buf->fastmap = NULL; + buf->buffer = NULL; + buf->allocated = 0; + if (regs) + { + regs->start = NULL; + regs->end = NULL; + } +} + +/*------------------------------------------------------------------. +| Regular expression version of index. Given two arguments, expand | +| to the index of the first match of the second argument (a regexp) | +| in the first. Expand to -1 if here is no match. Given a third | +| argument, it changes the expansion to this argument. | +`------------------------------------------------------------------*/ + +static void +m4_regexp (struct obstack *obs, int argc, token_data **argv) +{ + const char *victim; /* first argument */ + const char *regexp; /* regular expression */ + const char *repl; /* replacement string */ + + struct re_pattern_buffer buf; /* compiled regular expression */ + struct re_registers regs; /* for subexpression matches */ + const char *msg; /* error message from re_compile_pattern */ + int startpos; /* start position of match */ + int length; /* length of first argument */ + + if (bad_argc (argv[0], argc, 3, 4)) + { + /* builtin(`regexp') is blank, but regexp(`abc') is 0. */ + if (argc == 2) + shipout_int (obs, 0); + return; + } + + victim = TOKEN_DATA_TEXT (argv[1]); + regexp = TOKEN_DATA_TEXT (argv[2]); + + init_pattern_buffer (&buf, ®s); + msg = re_compile_pattern (regexp, strlen (regexp), &buf); + + if (msg != NULL) + { + M4ERROR ((warning_status, 0, + "bad regular expression: `%s': %s", regexp, msg)); + free_pattern_buffer (&buf, ®s); + return; + } + + length = strlen (victim); + /* Avoid overhead of allocating regs if we won't use it. */ + startpos = re_search (&buf, victim, length, 0, length, + argc == 3 ? NULL : ®s); + + if (startpos == -2) + M4ERROR ((warning_status, 0, + "error matching regular expression `%s'", regexp)); + else if (argc == 3) + shipout_int (obs, startpos); + else if (startpos >= 0) + { + repl = TOKEN_DATA_TEXT (argv[3]); + substitute (obs, victim, repl, ®s); + } + + free_pattern_buffer (&buf, ®s); +} + +/*--------------------------------------------------------------------------. +| Substitute all matches of a regexp occuring in a string. Each match of | +| the second argument (a regexp) in the first argument is changed to the | +| third argument, with \& substituted by the matched text, and \N | +| substituted by the text matched by the Nth parenthesized sub-expression. | +`--------------------------------------------------------------------------*/ + +static void +m4_patsubst (struct obstack *obs, int argc, token_data **argv) +{ + const char *victim; /* first argument */ + const char *regexp; /* regular expression */ + + struct re_pattern_buffer buf; /* compiled regular expression */ + struct re_registers regs; /* for subexpression matches */ + const char *msg; /* error message from re_compile_pattern */ + int matchpos; /* start position of match */ + int offset; /* current match offset */ + int length; /* length of first argument */ + + if (bad_argc (argv[0], argc, 3, 4)) + { + /* builtin(`patsubst') is blank, but patsubst(`abc') is abc. */ + if (argc == 2) + obstack_grow (obs, ARG (1), strlen (ARG (1))); + return; + } + + regexp = TOKEN_DATA_TEXT (argv[2]); + + init_pattern_buffer (&buf, ®s); + msg = re_compile_pattern (regexp, strlen (regexp), &buf); + + if (msg != NULL) + { + M4ERROR ((warning_status, 0, + "bad regular expression `%s': %s", regexp, msg)); + free (buf.buffer); + return; + } + + victim = TOKEN_DATA_TEXT (argv[1]); + length = strlen (victim); + + offset = 0; + while (offset <= length) + { + matchpos = re_search (&buf, victim, length, + offset, length - offset, ®s); + if (matchpos < 0) + { + + /* Match failed -- either error or there is no match in the + rest of the string, in which case the rest of the string is + copied verbatim. */ + + if (matchpos == -2) + M4ERROR ((warning_status, 0, + "error matching regular expression `%s'", regexp)); + else if (offset < length) + obstack_grow (obs, victim + offset, length - offset); + break; + } + + /* Copy the part of the string that was skipped by re_search (). */ + + if (matchpos > offset) + obstack_grow (obs, victim + offset, matchpos - offset); + + /* Handle the part of the string that was covered by the match. */ + + substitute (obs, victim, ARG (3), ®s); + + /* Update the offset to the end of the match. If the regexp + matched a null string, advance offset one more, to avoid + infinite loops. */ + + offset = regs.end[0]; + if (regs.start[0] == regs.end[0]) + obstack_1grow (obs, victim[offset++]); + } + obstack_1grow (obs, '\0'); + + free_pattern_buffer (&buf, ®s); +} + +/* Finally, a placeholder builtin. This builtin is not installed by + default, but when reading back frozen files, this is associated + with any builtin we don't recognize (for example, if the frozen + file was created with a changeword capable m4, but is then loaded + by a different m4 that does not support changeword). This way, we + can keep 'm4 -R' quiet in the common case that the user did not + know or care about the builtin when the frozen file was created, + while still flagging it as a potential error if an attempt is made + to actually use the builtin. */ + +/*--------------------------------------------------------------------. +| Issue a warning that this macro is a placeholder for an unsupported | +| builtin that was requested while reloading a frozen file. | +`--------------------------------------------------------------------*/ + +void +m4_placeholder (struct obstack *obs M4_GNUC_UNUSED, int argc, + token_data **argv) +{ + M4ERROR ((warning_status, 0, "\ +builtin `%s' requested by frozen file is not supported", ARG (0))); +} + +/*-------------------------------------------------------------------. +| This function handles all expansion of user defined and predefined | +| macros. It is called with an obstack OBS, where the macros | +| expansion will be placed, as an unfinished object. SYM points to | +| the macro definition, giving the expansion text. ARGC and ARGV | +| are the arguments, as usual. | +`-------------------------------------------------------------------*/ + +void +expand_user_macro (struct obstack *obs, symbol *sym, + int argc, token_data **argv) +{ + const char *text = SYMBOL_TEXT (sym); + int i; + while (1) + { + const char *dollar = strchr (text, '$'); + if (!dollar) + { + obstack_grow (obs, text, strlen (text)); + return; + } + obstack_grow (obs, text, dollar - text); + text = dollar; + switch (*++text) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if (no_gnu_extensions) + { + i = *text++ - '0'; + } + else + { + for (i = 0; isdigit (to_uchar (*text)); text++) + i = i*10 + (*text - '0'); + } + if (i < argc) + obstack_grow (obs, TOKEN_DATA_TEXT (argv[i]), + strlen (TOKEN_DATA_TEXT (argv[i]))); + break; + + case '#': /* number of arguments */ + shipout_int (obs, argc - 1); + text++; + break; + + case '*': /* all arguments */ + case '@': /* ... same, but quoted */ + dump_args (obs, argc, argv, ",", *text == '@'); + text++; + break; + + default: + obstack_1grow (obs, '$'); + break; + } + } +} |