| /* GNU m4 -- A simple macro processor |
| Copyright (C) 2000, 2004-2010, 2013-2014, 2017 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/>. |
| */ |
| |
| #include <config.h> |
| |
| /* Build using only the exported interfaces, unles NDEBUG is set, in |
| which case use private symbols to speed things up as much as possible. */ |
| #ifndef NDEBUG |
| # include <m4/m4module.h> |
| #else |
| # include "m4private.h" |
| #endif |
| |
| #include "modules/m4.h" |
| #include "quotearg.h" |
| #include "spawn-pipe.h" |
| #include "wait-process.h" |
| |
| /* Maintain each of the builtins implemented in this modules along |
| with their details in a single table for easy maintenance. |
| |
| function macros blind side minargs maxargs */ |
| #define builtin_functions \ |
| BUILTIN (__file__, false, false, false, 0, 0 ) \ |
| BUILTIN (__line__, false, false, false, 0, 0 ) \ |
| BUILTIN (__program__, false, false, false, 0, 0 ) \ |
| BUILTIN (builtin, true, true, false, 1, -1 ) \ |
| BUILTIN (changeresyntax,false,true, false, 1, 1 ) \ |
| BUILTIN (changesyntax,false, true, false, 1, -1 ) \ |
| BUILTIN (debugfile, false, false, false, 0, 1 ) \ |
| BUILTIN (debuglen, false, true, false, 1, 1 ) \ |
| BUILTIN (debugmode, false, false, false, 0, 1 ) \ |
| BUILTIN (esyscmd, false, true, true, 1, 1 ) \ |
| BUILTIN (format, false, true, false, 1, -1 ) \ |
| BUILTIN (indir, true, true, false, 1, -1 ) \ |
| BUILTIN (mkdtemp, false, true, false, 1, 1 ) \ |
| BUILTIN (patsubst, false, true, true, 2, 4 ) \ |
| BUILTIN (regexp, false, true, true, 2, 4 ) \ |
| BUILTIN (renamesyms, false, true, false, 2, 3 ) \ |
| BUILTIN (m4modules, false, false, false, 0, 0 ) \ |
| BUILTIN (m4symbols, true, false, false, 0, -1 ) \ |
| BUILTIN (syncoutput, false, true, false, 1, 1 ) \ |
| |
| |
| /* Generate prototypes for each builtin handler function. */ |
| #define BUILTIN(handler, macros, blind, side, min, max) M4BUILTIN (handler) |
| builtin_functions |
| #undef BUILTIN |
| |
| |
| /* Generate a table for mapping m4 symbol names to handler functions. */ |
| static const m4_builtin m4_builtin_table[] = |
| { |
| #define BUILTIN(handler, macros, blind, side, min, max) \ |
| M4BUILTIN_ENTRY (handler, #handler, macros, blind, side, min, max) |
| |
| builtin_functions |
| #undef BUILTIN |
| |
| { NULL, NULL, 0, 0, 0 }, |
| }; |
| |
| |
| /* A table for mapping m4 symbol names to simple expansion text. */ |
| static const m4_macro m4_macro_table[] = |
| { |
| /* name text min max */ |
| #if UNIX |
| { "__unix__", "", 0, 0 }, |
| #endif |
| #if W32_NATIVE |
| { "__windows__", "", 0, 0 }, |
| #endif |
| #if OS2 |
| { "__os2__", "", 0, 0 }, |
| #endif |
| { "__gnu__", "", 0, 0 }, |
| { "__m4_version__", VERSION,0, 0 }, |
| |
| { NULL, NULL, 0, 0 }, |
| }; |
| |
| |
| void |
| include_gnu (m4 *context, m4_module *module, m4_obstack *obs) |
| { |
| m4_install_builtins (context, module, m4_builtin_table); |
| m4_install_macros (context, module, m4_macro_table); |
| } |
| |
| |
| |
| /* Regular expressions. Reuse re_registers among multiple |
| re_pattern_buffer allocations to reduce malloc usage. */ |
| |
| /* Maybe this is worth making runtime tunable. Too small, and nothing |
| gets cached because the working set of active regex is larger than |
| the cache, and we are always swapping out entries. Too large, and |
| the time spent searching the cache for a match overtakes the time |
| saved by caching. For now, this size proved reasonable for the |
| typical working set of Autoconf 2.62. */ |
| #define REGEX_CACHE_SIZE 16 |
| |
| /* Structure for using a compiled regex, as well as making it easier |
| to cache frequently used expressions. */ |
| typedef struct { |
| unsigned count; /* usage counter */ |
| int resyntax; /* flavor of regex */ |
| size_t len; /* length of string */ |
| char *str; /* copy of compiled string */ |
| struct re_pattern_buffer *pat; /* compiled regex, allocated */ |
| struct re_registers regs; /* match registers, reused */ |
| } m4_pattern_buffer; |
| |
| /* Storage for the cache of regular expressions. */ |
| static m4_pattern_buffer regex_cache[REGEX_CACHE_SIZE]; |
| |
| /* Compile a REGEXP of length LEN using the RESYNTAX flavor, and |
| return the buffer. On error, report the problem on behalf of |
| CALLER, and return NULL. */ |
| |
| static m4_pattern_buffer * |
| regexp_compile (m4 *context, const m4_call_info *caller, const char *regexp, |
| size_t len, int resyntax) |
| { |
| /* regex_cache is guaranteed to start life 0-initialized, which |
| works in the algorithm below. |
| |
| FIXME - this method is not reentrant, since re_compile_pattern |
| mallocs memory, depends on the global variable re_syntax_options |
| for its syntax (but at least the compiled regex remembers its |
| syntax even if the global variable changes later), and since we |
| use a static variable. To be reentrant, we would need a mutex in |
| this method, and move the storage for regex_cache into context. */ |
| |
| const char *msg; /* error message from re_compile_pattern */ |
| int i; /* iterator */ |
| m4_pattern_buffer *victim; /* cache slot to replace */ |
| unsigned victim_count; /* track which victim to replace */ |
| struct re_pattern_buffer *pat;/* newly compiled regex */ |
| |
| /* First, check if REGEXP is already cached with the given RESYNTAX. |
| If so, increase its use count and return it. */ |
| for (i = 0; i < REGEX_CACHE_SIZE; i++) |
| if (len == regex_cache[i].len && resyntax == regex_cache[i].resyntax |
| && regex_cache[i].str && memcmp (regexp, regex_cache[i].str, len) == 0) |
| { |
| regex_cache[i].count++; |
| return ®ex_cache[i]; |
| } |
| |
| /* Next, check if REGEXP can be compiled. */ |
| pat = (struct re_pattern_buffer *) xzalloc (sizeof *pat); |
| re_set_syntax (resyntax); |
| msg = re_compile_pattern (regexp, len, pat); |
| |
| if (msg != NULL) |
| { |
| m4_warn (context, 0, caller, _("bad regular expression %s: %s"), |
| quotearg_style_mem (locale_quoting_style, regexp, len), msg); |
| regfree (pat); |
| free (pat); |
| return NULL; |
| } |
| /* Use a fastmap for speed; it is freed by regfree. */ |
| pat->fastmap = xcharalloc (UCHAR_MAX + 1); |
| |
| /* Now, find a victim slot. Decrease the count of all entries, then |
| prime the count of the victim slot at REGEX_CACHE_SIZE. This |
| way, frequently used entries and newly created entries are least |
| likely to be victims next time we have a cache miss. */ |
| victim = regex_cache; |
| victim_count = victim->count; |
| if (victim_count) |
| victim->count--; |
| for (i = 1; i < REGEX_CACHE_SIZE; i++) |
| { |
| if (regex_cache[i].count < victim_count) |
| { |
| victim_count = regex_cache[i].count; |
| victim = ®ex_cache[i]; |
| } |
| if (regex_cache[i].count) |
| regex_cache[i].count--; |
| } |
| victim->count = REGEX_CACHE_SIZE; |
| victim->resyntax = resyntax; |
| victim->len = len; |
| if (victim->str) |
| { |
| free (victim->str); |
| regfree (victim->pat); |
| free (victim->pat); |
| } |
| victim->str = xstrdup (regexp); |
| victim->pat = pat; |
| re_set_registers (pat, &victim->regs, victim->regs.num_regs, |
| victim->regs.start, victim->regs.end); |
| return victim; |
| } |
| |
| |
| /* Wrap up GNU Regex re_search call to work with an m4_pattern_buffer. |
| If NO_SUB, then storing matches in buf->regs is not necessary. */ |
| |
| static regoff_t |
| regexp_search (m4_pattern_buffer *buf, const char *string, const int size, |
| const int start, const int range, bool no_sub) |
| { |
| return re_search (buf->pat, string, size, start, range, |
| no_sub ? NULL : &buf->regs); |
| } |
| |
| |
| /* Function to perform substitution by regular expressions. Used by |
| the builtins regexp, patsubst and renamesyms. The changed text is |
| placed on the obstack OBS. The substitution is REPL of length |
| REPL_LEN, with \& substituted by this part of VICTIM matched by the |
| last whole regular expression, and \N substituted by the text |
| matched by the Nth parenthesized sub-expression in BUF. Any |
| warnings are issued on behalf of CALLER. BUF may be NULL for the |
| empty regex. */ |
| |
| static void |
| substitute (m4 *context, m4_obstack *obs, const m4_call_info *caller, |
| const char *victim, const char *repl, size_t repl_len, |
| m4_pattern_buffer *buf) |
| { |
| int ch; |
| while (1) |
| { |
| const char *backslash = (char *) memchr (repl, '\\', repl_len); |
| if (!backslash) |
| { |
| obstack_grow (obs, repl, repl_len); |
| return; |
| } |
| obstack_grow (obs, repl, backslash - repl); |
| repl_len -= backslash - repl + 1; |
| if (!repl_len) |
| { |
| m4_warn (context, 0, caller, |
| _("trailing \\ ignored in replacement")); |
| return; |
| } |
| repl = backslash + 1; |
| ch = *repl++; |
| repl_len--; |
| switch (ch) |
| { |
| case '&': |
| if (buf) |
| obstack_grow (obs, victim + buf->regs.start[0], |
| buf->regs.end[0] - buf->regs.start[0]); |
| break; |
| |
| case '1': case '2': case '3': case '4': case '5': case '6': |
| case '7': case '8': case '9': |
| ch -= '0'; |
| if (!buf || buf->pat->re_nsub < ch) |
| m4_warn (context, 0, caller, _("sub-expression %d not present"), |
| ch); |
| else if (buf->regs.end[ch] > 0) |
| obstack_grow (obs, victim + buf->regs.start[ch], |
| buf->regs.end[ch] - buf->regs.start[ch]); |
| break; |
| |
| default: |
| obstack_1grow (obs, ch); |
| break; |
| } |
| } |
| } |
| |
| |
| /* For each match against REGEXP of length REGEXP_LEN (precompiled in |
| BUF as returned by regexp_compile) in VICTIM of length LEN, |
| substitute REPLACE of length REPL_LEN. Non-matching characters are |
| copied verbatim, and the result copied to the obstack. Errors are |
| reported on behalf of CALLER. Return true if a substitution was |
| made. If OPTIMIZE is set, don't worry about copying the input if |
| no changes are made. */ |
| |
| static bool |
| regexp_substitute (m4 *context, m4_obstack *obs, const m4_call_info *caller, |
| const char *victim, size_t len, const char *regexp, |
| size_t regexp_len, m4_pattern_buffer *buf, |
| const char *replace, size_t repl_len, bool optimize) |
| { |
| regoff_t matchpos = 0; /* start position of match */ |
| size_t offset = 0; /* current match offset */ |
| bool subst = !optimize; /* if a substitution has been made */ |
| |
| while (offset <= len) |
| { |
| matchpos = regexp_search (buf, victim, len, offset, len - offset, |
| false); |
| |
| 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) |
| m4_error (context, 0, 0, caller, |
| _("problem matching regular expression %s"), |
| quotearg_style_mem (locale_quoting_style, regexp, |
| regexp_len)); |
| else if (offset < len && subst) |
| obstack_grow (obs, victim + offset, len - 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 (context, obs, caller, victim, replace, repl_len, buf); |
| subst = true; |
| |
| /* 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 = buf->regs.end[0]; |
| if (buf->regs.start[0] == buf->regs.end[0]) |
| { |
| if (offset < len) |
| obstack_1grow (obs, victim[offset]); |
| offset++; |
| } |
| } |
| |
| return subst; |
| } |
| |
| |
| |
| /** |
| * __file__ |
| **/ |
| M4BUILTIN_HANDLER (__file__) |
| { |
| m4_shipout_string (context, obs, m4_get_current_file (context), SIZE_MAX, |
| true); |
| } |
| |
| |
| /** |
| * __line__ |
| **/ |
| M4BUILTIN_HANDLER (__line__) |
| { |
| m4_shipout_int (obs, m4_get_current_line (context)); |
| } |
| |
| |
| /** |
| * __program__ |
| **/ |
| M4BUILTIN_HANDLER (__program__) |
| { |
| m4_shipout_string (context, obs, m4_get_program_name (), SIZE_MAX, true); |
| } |
| |
| |
| /* 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. A |
| special form allows one to retrieve the special token that defn |
| would normally return, even if that builtin is not currently |
| defined and hence can't be passed to defn. */ |
| |
| /** |
| * builtin(MACRO, [...]) |
| * builtin(defn(`builtin'), MACRO) |
| **/ |
| M4BUILTIN_HANDLER (builtin) |
| { |
| const m4_call_info *me = m4_arg_info (argv); |
| const char *name; |
| size_t len; |
| m4_symbol_value *value = NULL; |
| |
| if (!m4_is_arg_text (argv, 1)) |
| { |
| assert (m4_is_arg_func (argv, 1)); |
| if (m4_arg_func (argv, 1) == builtin_builtin) |
| { |
| if (m4_bad_argc (context, argc, me, 2, 2, false)) |
| return; |
| if (!m4_is_arg_text (argv, 2)) |
| { |
| m4_warn (context, 0, me, _("invalid macro name ignored")); |
| return; |
| } |
| name = M4ARG (2); |
| len = M4ARGLEN (2); |
| if (len == strlen (name)) |
| value = m4_builtin_find_by_name (context, NULL, name); |
| if (value) |
| { |
| m4_push_builtin (context, obs, value); |
| free (value); |
| } |
| else if (m4_is_debug_bit (context, M4_DEBUG_TRACE_DEREF)) |
| m4_warn (context, 0, me, _("undefined builtin %s"), |
| quotearg_style_mem (locale_quoting_style, name, len)); |
| } |
| else |
| m4_warn (context, 0, me, _("invalid macro name ignored")); |
| } |
| else |
| { |
| name = M4ARG (1); |
| len = M4ARGLEN (1); |
| if (len == strlen (name)) |
| value = m4_builtin_find_by_name (context, NULL, name); |
| if (value == NULL) |
| { |
| if (m4_is_debug_bit (context, M4_DEBUG_TRACE_DEREF)) |
| m4_warn (context, 0, me, _("undefined builtin %s"), |
| quotearg_style_mem (locale_quoting_style, name, len)); |
| } |
| else |
| { |
| const m4_builtin *bp = m4_get_symbol_value_builtin (value); |
| m4_macro_args *new_argv; |
| bool flatten = (bp->flags & M4_BUILTIN_FLATTEN_ARGS) != 0; |
| new_argv = m4_make_argv_ref (context, argv, name, len, flatten, |
| false); |
| if (!m4_bad_argc (context, argc - 1, m4_arg_info (new_argv), |
| bp->min_args, bp->max_args, |
| (bp->flags & M4_BUILTIN_SIDE_EFFECT) != 0)) |
| bp->func (context, obs, argc - 1, new_argv); |
| free (value); |
| } |
| } |
| } |
| |
| |
| /* Change the current regexp syntax to SPEC of length LEN, or report |
| failure on behalf of CALLER. Currently this affects the builtins: |
| `patsubst', `regexp' and `renamesyms'. */ |
| |
| static int |
| m4_resyntax_encode_safe (m4 *context, const m4_call_info *caller, |
| const char *spec, size_t len) |
| { |
| int resyntax; |
| |
| if (strlen (spec) < len) |
| resyntax = -1; |
| else |
| resyntax = m4_regexp_syntax_encode (spec); |
| |
| if (resyntax < 0) |
| m4_warn (context, 0, caller, _("bad syntax-spec: %s"), |
| quotearg_style_mem (locale_quoting_style, spec, len)); |
| |
| return resyntax; |
| } |
| |
| |
| /** |
| * changeresyntax(RESYNTAX-SPEC) |
| **/ |
| M4BUILTIN_HANDLER (changeresyntax) |
| { |
| int resyntax = m4_resyntax_encode_safe (context, m4_arg_info (argv), |
| M4ARG (1), M4ARGLEN (1)); |
| |
| if (resyntax >= 0) |
| m4_set_regexp_syntax_opt (context, resyntax); |
| } |
| |
| |
| /* Change the current input syntax. The function m4_set_syntax () |
| lives in syntax.c. Any changes to comment delimiters and quotes |
| made here will be overridden by a call to `changecom' or |
| `changequote'. */ |
| |
| /** |
| * changesyntax(SYNTAX-SPEC, ...) |
| **/ |
| M4BUILTIN_HANDLER (changesyntax) |
| { |
| const m4_call_info *me = m4_arg_info (argv); |
| M4_MODULE_IMPORT (m4, m4_expand_ranges); |
| |
| if (m4_expand_ranges) |
| { |
| size_t i; |
| for (i = 1; i < argc; i++) |
| { |
| size_t len = M4ARGLEN (i); |
| const char *spec; |
| char key; |
| char action; |
| |
| if (!len) |
| { |
| m4_reset_syntax (M4SYNTAX); |
| continue; |
| } |
| spec = M4ARG (i); |
| key = *spec++; |
| len--; |
| action = len ? *spec : '\0'; |
| switch (action) |
| { |
| case '-': |
| case '+': |
| case '=': |
| spec++; |
| len--; |
| break; |
| case '\0': |
| if (!len) |
| break; |
| /* fall through */ |
| default: |
| action = '='; |
| break; |
| } |
| if (len) |
| spec = m4_expand_ranges (spec, &len, m4_arg_scratch (context)); |
| if (m4_set_syntax (M4SYNTAX, key, action, spec, len) < 0) |
| m4_warn (context, 0, me, _("undefined syntax code: %s"), |
| quotearg_style_mem (locale_quoting_style, &key, 1)); |
| } |
| } |
| else |
| assert (!"Unable to import from m4 module"); |
| } |
| |
| |
| /* Specify the destination of the debugging output. With one argument, the |
| argument is taken as a file name, with no arguments, revert to stderr. */ |
| |
| /** |
| * debugfile([FILENAME]) |
| **/ |
| M4BUILTIN_HANDLER (debugfile) |
| { |
| const m4_call_info *me = m4_arg_info (argv); |
| |
| if (argc == 1) |
| m4_debug_set_output (context, me, NULL); |
| else if (m4_get_safer_opt (context) && !m4_arg_empty (argv, 1)) |
| m4_error (context, 0, 0, me, _("disabled by --safer")); |
| else |
| { |
| const char *str = M4ARG (1); |
| size_t len = M4ARGLEN (1); |
| if (strlen (str) < len) |
| m4_warn (context, 0, me, _("argument %s truncated"), |
| quotearg_style_mem (locale_quoting_style, str, len)); |
| if (!m4_debug_set_output (context, me, str)) |
| m4_warn (context, errno, me, _("cannot set debug file %s"), |
| quotearg_style (locale_quoting_style, str)); |
| } |
| } |
| |
| |
| /* On-the-fly control of debug length. It takes one integer |
| argument. */ |
| |
| /** |
| * debuglen(LEN) |
| **/ |
| M4BUILTIN_HANDLER (debuglen) |
| { |
| int i; |
| size_t s; |
| if (!m4_numeric_arg (context, m4_arg_info (argv), M4ARG (1), M4ARGLEN (1), |
| &i)) |
| return; |
| /* FIXME - make m4_numeric_arg more powerful - we want to accept |
| suffixes, and limit the result to size_t. */ |
| s = i <= 0 ? SIZE_MAX : i; |
| m4_set_max_debug_arg_length_opt (context, s); |
| } |
| |
| /* 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. */ |
| |
| /** |
| * debugmode([FLAGS]) |
| **/ |
| M4BUILTIN_HANDLER (debugmode) |
| { |
| const char* mode = M4ARG (1); |
| size_t len = M4ARGLEN (1); |
| if (argc == 1) |
| m4_set_debug_level_opt (context, 0); |
| else if (m4_debug_decode (context, mode, len) < 0) |
| m4_warn (context, 0, m4_arg_info (argv), |
| _("bad debug flags: %s"), |
| quotearg_style_mem (locale_quoting_style, mode, len)); |
| } |
| |
| |
| /* Same as the sysymd builtin from m4.c module, but expand to the |
| output of SHELL-COMMAND. */ |
| |
| /** |
| * esyscmd(SHELL-COMMAND) |
| **/ |
| |
| M4BUILTIN_HANDLER (esyscmd) |
| { |
| const m4_call_info *me = m4_arg_info (argv); |
| const char *cmd = M4ARG (1); |
| size_t len = M4ARGLEN (1); |
| M4_MODULE_IMPORT (m4, m4_set_sysval); |
| M4_MODULE_IMPORT (m4, m4_sysval_flush); |
| |
| if (m4_set_sysval && m4_sysval_flush) |
| { |
| pid_t child; |
| int fd; |
| FILE *pin; |
| int status; |
| int sig_status; |
| const char *prog_args[4] = { "sh", "-c" }; |
| const char *caller; |
| |
| if (m4_get_safer_opt (context)) |
| { |
| m4_error (context, 0, 0, me, _("disabled by --safer")); |
| return; |
| } |
| if (strlen (cmd) != len) |
| m4_warn (context, 0, me, _("argument %s truncated"), |
| quotearg_style_mem (locale_quoting_style, cmd, len)); |
| |
| /* Optimize the empty command. */ |
| if (!*cmd) |
| { |
| m4_set_sysval (0); |
| return; |
| } |
| |
| m4_sysval_flush (context, false); |
| #if W32_NATIVE |
| if (strstr (M4_SYSCMD_SHELL, "cmd")) |
| { |
| prog_args[0] = "cmd"; |
| prog_args[1] = "/c"; |
| } |
| #endif |
| prog_args[2] = cmd; |
| caller = m4_info_name (me); |
| errno = 0; |
| child = create_pipe_in (caller, M4_SYSCMD_SHELL, (char **) prog_args, |
| NULL, false, true, false, &fd); |
| if (child == -1) |
| { |
| m4_error (context, 0, errno, me, _("cannot run command %s"), |
| quotearg_style (locale_quoting_style, cmd)); |
| m4_set_sysval (127); |
| return; |
| } |
| #if OS2 |
| /* On OS/2 kLIBC, fdopen() creates a stream in a mode of a file |
| descriptor. So incldue "t" to open stream in a text mode explicitly. */ |
| pin = fdopen (fd, "rt"); |
| #else |
| pin = fdopen (fd, "r"); |
| #endif |
| if (!pin) |
| { |
| m4_error (context, 0, errno, me, _("cannot run command %s"), |
| quotearg_style (locale_quoting_style, cmd)); |
| m4_set_sysval (127); |
| close (fd); |
| return; |
| } |
| while (1) |
| { |
| size_t avail = obstack_room (obs); |
| if (!avail) |
| { |
| int ch = getc (pin); |
| if (ch == EOF) |
| break; |
| obstack_1grow (obs, ch); |
| } |
| else |
| { |
| size_t len = fread (obstack_next_free (obs), 1, avail, pin); |
| if (len <= 0) |
| break; |
| obstack_blank_fast (obs, len); |
| } |
| } |
| if (ferror (pin) || fclose (pin)) |
| m4_error (context, EXIT_FAILURE, errno, me, |
| _("cannot read pipe to command %s"), |
| quotearg_style (locale_quoting_style, cmd)); |
| errno = 0; |
| status = wait_subprocess (child, caller, false, true, true, false, |
| &sig_status); |
| if (sig_status) |
| { |
| assert (status == 127); |
| m4_set_sysval (sig_status << 8); |
| } |
| else |
| { |
| if (status == 127 && errno) |
| m4_error (context, 0, errno, me, _("cannot run command %s"), |
| quotearg_style (locale_quoting_style, cmd)); |
| m4_set_sysval (status); |
| } |
| } |
| else |
| assert (!"Unable to import from m4 module"); |
| } |
| |
| |
| /* Frontend for printf like formatting. The function format () lives in |
| the file format.c. */ |
| |
| #include "format.c" |
| |
| /** |
| * format(FORMAT-STRING, [...]) |
| **/ |
| M4BUILTIN_HANDLER (format) |
| { |
| format (context, obs, argc, argv); |
| } |
| |
| |
| /* 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. */ |
| |
| /** |
| * indir(MACRO, [...]) |
| **/ |
| M4BUILTIN_HANDLER (indir) |
| { |
| const m4_call_info *me = m4_arg_info (argv); |
| if (!m4_is_arg_text (argv, 1)) |
| m4_warn (context, 0, me, _("invalid macro name ignored")); |
| else |
| { |
| const char *name = M4ARG (1); |
| size_t len = M4ARGLEN (1); |
| m4_symbol *symbol = m4_symbol_lookup (M4SYMTAB, name, len); |
| |
| if (symbol == NULL) |
| { |
| if (m4_is_debug_bit (context, M4_DEBUG_TRACE_DEREF)) |
| m4_warn (context, 0, me, _("undefined macro %s"), |
| quotearg_style_mem (locale_quoting_style, name, len)); |
| } |
| else |
| { |
| m4_macro_args *new_argv; |
| m4_symbol_value *value = m4_get_symbol_value (symbol); |
| new_argv = m4_make_argv_ref (context, argv, name, len, |
| m4_symbol_flatten_args (symbol), |
| m4_get_symbol_traced (symbol)); |
| m4_trace_prepare (context, m4_arg_info (new_argv), value); |
| m4_macro_call (context, value, obs, new_argv); |
| } |
| } |
| } |
| |
| |
| /* The builtin "mkdtemp" allows creation of temporary directories. */ |
| |
| /** |
| * mkdtemp(TEMPLATE) |
| **/ |
| M4BUILTIN_HANDLER (mkdtemp) |
| { |
| M4_MODULE_IMPORT (m4, m4_make_temp); |
| |
| if (m4_make_temp) |
| m4_make_temp (context, obs, m4_arg_info (argv), M4ARG (1), M4ARGLEN (1), |
| true); |
| else |
| assert (!"Unable to import from m4 module"); |
| } |
| |
| |
| /* Substitute all matches of a regexp occurring in a string. Each |
| match of the second argument (a regexp) in the first argument is |
| changed to the optional third argument, with \& substituted by the |
| matched text, and \N substituted by the text matched by the Nth |
| parenthesized sub-expression. The optional fourth argument changes |
| the regex flavor. */ |
| |
| /** |
| * patsubst(VICTIM, REGEXP, [REPLACEMENT], [RESYNTAX]) |
| **/ |
| M4BUILTIN_HANDLER (patsubst) |
| { |
| const m4_call_info *me = m4_arg_info (argv); |
| const char *pattern; /* regular expression */ |
| const char *replace; /* replacement */ |
| m4_pattern_buffer *buf; /* compiled regular expression */ |
| int resyntax; |
| |
| resyntax = m4_get_regexp_syntax_opt (context); |
| if (argc >= 5) /* additional args ignored */ |
| { |
| resyntax = m4_resyntax_encode_safe (context, me, M4ARG (4), |
| M4ARGLEN (4)); |
| if (resyntax < 0) |
| return; |
| } |
| |
| /* The empty regex matches everywhere, but if there is no |
| replacement, we need not waste time with it. */ |
| if (m4_arg_empty (argv, 2) && m4_arg_empty (argv, 3)) |
| { |
| m4_push_arg (context, obs, argv, 1); |
| return; |
| } |
| |
| pattern = M4ARG (2); |
| replace = M4ARG (3); |
| |
| buf = regexp_compile (context, me, pattern, M4ARGLEN (2), resyntax); |
| if (!buf) |
| return; |
| |
| regexp_substitute (context, obs, me, M4ARG (1), M4ARGLEN (1), pattern, |
| M4ARGLEN (2), buf, replace, M4ARGLEN (3), false); |
| } |
| |
| |
| /* 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 there is no match. Given a third |
| argument, a match is substituted according to this argument. The |
| optional fourth argument changes the regex flavor. */ |
| |
| /** |
| * regexp(VICTIM, REGEXP, RESYNTAX) |
| * regexp(VICTIM, REGEXP, [REPLACEMENT], [RESYNTAX]) |
| **/ |
| M4BUILTIN_HANDLER (regexp) |
| { |
| const m4_call_info *me = m4_arg_info (argv); |
| const char *victim; /* string to search */ |
| const char *pattern; /* regular expression */ |
| const char *replace; /* optional replacement string */ |
| m4_pattern_buffer *buf; /* compiled regular expression */ |
| regoff_t startpos; /* start position of match */ |
| size_t len; /* length of first argument */ |
| int resyntax; |
| |
| pattern = M4ARG (2); |
| replace = M4ARG (3); |
| resyntax = m4_get_regexp_syntax_opt (context); |
| |
| if (argc == 4) |
| { |
| resyntax = m4_regexp_syntax_encode (replace); |
| |
| /* The first case is the most difficult, because the empty string |
| is a valid RESYNTAX, yet we want `regexp(aab, a*, )' to return |
| an empty string as per M4 1.4.x. */ |
| |
| if (m4_arg_empty (argv, 3) || (resyntax < 0)) |
| /* regexp(VICTIM, REGEXP, REPLACEMENT) */ |
| resyntax = m4_get_regexp_syntax_opt (context); |
| else |
| /* regexp(VICTIM, REGEXP, RESYNTAX) */ |
| replace = NULL; |
| } |
| else if (argc >= 5) |
| { |
| /* regexp(VICTIM, REGEXP, REPLACEMENT, RESYNTAX) */ |
| resyntax = m4_resyntax_encode_safe (context, me, M4ARG (4), |
| M4ARGLEN (4)); |
| if (resyntax < 0) |
| return; |
| } |
| else |
| /* regexp(VICTIM, REGEXP) */ |
| replace = NULL; |
| |
| if (m4_arg_empty (argv, 2)) |
| { |
| /* The empty regex matches everything. */ |
| if (replace) |
| substitute (context, obs, me, M4ARG (1), replace, M4ARGLEN (3), NULL); |
| else |
| m4_shipout_int (obs, 0); |
| return; |
| } |
| |
| buf = regexp_compile (context, me, pattern, M4ARGLEN (2), resyntax); |
| if (!buf) |
| return; |
| |
| victim = M4ARG (1); |
| len = M4ARGLEN (1); |
| startpos = regexp_search (buf, victim, len, 0, len, replace == NULL); |
| |
| if (startpos == -2) |
| { |
| m4_error (context, 0, 0, me, _("problem matching regular expression %s"), |
| quotearg_style_mem (locale_quoting_style, pattern, |
| M4ARGLEN (2))); |
| return; |
| } |
| |
| if (replace == NULL) |
| m4_shipout_int (obs, startpos); |
| else if (startpos >= 0) |
| substitute (context, obs, me, victim, replace, M4ARGLEN (3), buf); |
| } |
| |
| |
| /* Rename all current symbols that match REGEXP according to the |
| REPLACEMENT specification. */ |
| |
| /** |
| * renamesyms(REGEXP, REPLACEMENT, [RESYNTAX]) |
| **/ |
| M4BUILTIN_HANDLER (renamesyms) |
| { |
| M4_MODULE_IMPORT (m4, m4_dump_symbols); |
| |
| if (m4_dump_symbols) |
| { |
| const m4_call_info *me = m4_arg_info (argv); |
| const char *regexp; /* regular expression string */ |
| size_t regexp_len; |
| const char *replace; /* replacement expression string */ |
| size_t replace_len; |
| |
| m4_pattern_buffer *buf; /* compiled regular expression */ |
| |
| m4_dump_symbol_data data; |
| |
| int resyntax; |
| |
| regexp = M4ARG (1); |
| regexp_len = M4ARGLEN (1); |
| replace = M4ARG (2); |
| replace_len = M4ARGLEN (2); |
| |
| resyntax = m4_get_regexp_syntax_opt (context); |
| if (argc >= 4) |
| { |
| resyntax = m4_resyntax_encode_safe (context, me, M4ARG (3), |
| M4ARGLEN (3)); |
| if (resyntax < 0) |
| return; |
| } |
| |
| buf = regexp_compile (context, me, regexp, regexp_len, resyntax); |
| if (!buf) |
| return; |
| |
| data.obs = m4_arg_scratch (context); |
| m4_dump_symbols (context, &data, 1, argv, false); |
| |
| for (; data.size > 0; --data.size, data.base++) |
| { |
| const m4_string *key = &data.base[0]; |
| |
| if (regexp_substitute (context, data.obs, me, key->str, key->len, |
| regexp, regexp_len, buf, replace, replace_len, |
| true)) |
| { |
| size_t newlen = obstack_object_size (data.obs); |
| m4_symbol_rename (M4SYMTAB, key->str, key->len, |
| (char *) obstack_finish (data.obs), newlen); |
| } |
| } |
| } |
| else |
| assert (!"Unable to import from m4 module"); |
| } |
| |
| |
| /** |
| * m4modules() |
| **/ |
| M4BUILTIN_HANDLER (m4modules) |
| { |
| /* The expansion of this builtin is a comma separated list of |
| loaded modules. */ |
| m4_module *module = m4_module_next (context, NULL); |
| |
| if (module) |
| do |
| { |
| m4_shipout_string (context, obs, m4_get_module_name (module), SIZE_MAX, |
| true); |
| |
| if ((module = m4_module_next (context, module))) |
| obstack_1grow (obs, ','); |
| } |
| while (module); |
| } |
| |
| |
| /* Implementation of "m4symbols". It builds up a table of pointers to |
| symbols, sorts it and ships out the symbol names. */ |
| |
| /** |
| * m4symbols([...]) |
| **/ |
| M4BUILTIN_HANDLER (m4symbols) |
| { |
| M4_MODULE_IMPORT (m4, m4_dump_symbols); |
| |
| if (m4_dump_symbols) |
| { |
| m4_dump_symbol_data data; |
| |
| data.obs = m4_arg_scratch (context); |
| m4_dump_symbols (context, &data, argc, argv, false); |
| |
| for (; data.size > 0; --data.size, data.base++) |
| { |
| m4_shipout_string (context, obs, data.base->str, data.base->len, |
| true); |
| if (data.size > 1) |
| obstack_1grow (obs, ','); |
| } |
| } |
| else |
| assert (!"Unable to import from m4 module"); |
| } |
| |
| |
| /* This contains macro which implements syncoutput() which takes one arg |
| 1, on, yes - turn on sync lines |
| 0, off, no, blank - turn off sync lines |
| everything else is silently ignored */ |
| |
| /** |
| * syncoutput(TRUTH) |
| **/ |
| M4BUILTIN_HANDLER (syncoutput) |
| { |
| bool value = m4_get_syncoutput_opt (context); |
| value = m4_parse_truth_arg (context, m4_arg_info (argv), M4ARG (1), |
| M4ARGLEN (1), value); |
| m4_set_syncoutput_opt (context, value); |
| } |