/* GNU m4 -- A simple macro processor Copyright (C) 2000, 2002-2004, 2006-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 . */ #include /* Build using only the exported interfaces, unless NDEBUG is set, in which case use private symbols to speed things up as much as possible. */ #ifndef NDEBUG # include #else # include "m4private.h" #endif #include "execute.h" #include "memchr2.h" #include "memcmp2.h" #include "quotearg.h" #include "stdlib--.h" #include "tempname.h" #include "unistd--.h" #include extern void m4_set_sysval (int); extern void m4_sysval_flush (m4 *, bool); extern void m4_dump_symbols (m4 *, m4_dump_symbol_data *, size_t, m4_macro_args *, bool); extern const char *m4_expand_ranges (const char *, size_t *, m4_obstack *); extern void m4_make_temp (m4 *, m4_obstack *, const m4_call_info *, const char *, size_t, bool); /* 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 (changecom, false, false, false, 0, 2 ) \ BUILTIN (changequote, false, false, false, 0, 2 ) \ BUILTIN (decr, false, true, true, 1, 1 ) \ BUILTIN (define, true, true, false, 1, 2 ) \ BUILTIN (defn, true, true, false, 1, -1 ) \ BUILTIN (divert, false, false, false, 0, 2 ) \ BUILTIN (divnum, false, false, false, 0, 0 ) \ BUILTIN (dnl, false, false, false, 0, 0 ) \ BUILTIN (dumpdef, true, false, false, 0, -1 ) \ BUILTIN (errprint, false, true, false, 1, -1 ) \ BUILTIN (eval, false, true, true, 1, 3 ) \ BUILTIN (ifdef, true, true, false, 2, 3 ) \ BUILTIN (ifelse, true, true, false, 1, -1 ) \ BUILTIN (include, false, true, false, 1, 1 ) \ BUILTIN (incr, false, true, true, 1, 1 ) \ BUILTIN (index, false, true, true, 2, 3 ) \ BUILTIN (len, false, true, true, 1, 1 ) \ BUILTIN (m4exit, false, false, false, 0, 1 ) \ BUILTIN (m4wrap, true, true, false, 1, -1 ) \ BUILTIN (maketemp, false, true, false, 1, 1 ) \ BUILTIN (mkstemp, false, true, false, 1, 1 ) \ BUILTIN (popdef, true, true, false, 1, -1 ) \ BUILTIN (pushdef, true, true, false, 1, 2 ) \ BUILTIN (shift, true, true, false, 1, -1 ) \ BUILTIN (sinclude, false, true, false, 1, 1 ) \ BUILTIN (substr, false, true, true, 2, 4 ) \ BUILTIN (syscmd, false, true, true, 1, 1 ) \ BUILTIN (sysval, false, false, false, 0, 0 ) \ BUILTIN (traceoff, true, false, false, 0, -1 ) \ BUILTIN (traceon, true, false, false, 0, -1 ) \ BUILTIN (translit, false, true, true, 2, 3 ) \ BUILTIN (undefine, true, true, false, 1, -1 ) \ BUILTIN (undivert, false, false, false, 0, -1 ) \ typedef intmax_t number; typedef uintmax_t unumber; static void include (m4 *context, m4_obstack *obs, size_t argc, m4_macro_args *argv, bool silent); static int dumpdef_cmp_CB (const void *s1, const void *s2); static void * dump_symbol_CB (m4_symbol_table *, const char *, size_t, m4_symbol *symbol, void *userdata); static const char *ntoa (number value, int radix); static void numb_obstack (m4_obstack *obs, number value, int radix, int min); /* 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 }, }; void include_m4 (m4 *context, m4_module *module, m4_obstack *obs) { m4_install_builtins (context, module, m4_builtin_table); } /* 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 builtin_MACRONAME (m4_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. */ M4BUILTIN_HANDLER (define) { const m4_call_info *me = m4_arg_info (argv); if (m4_is_arg_text (argv, 1)) { m4_symbol_value *value = m4_symbol_value_create (); if (m4_symbol_value_copy (context, value, m4_arg_symbol (argv, 2))) m4_warn (context, 0, me, _("cannot concatenate builtins")); m4_symbol_define (M4SYMTAB, M4ARG (1), M4ARGLEN (1), value); } else m4_warn (context, 0, me, _("invalid macro name ignored")); } M4BUILTIN_HANDLER (undefine) { size_t i; for (i = 1; i < argc; i++) if (m4_symbol_value_lookup (context, argv, i, true)) m4_symbol_delete (M4SYMTAB, M4ARG (i), M4ARGLEN (i)); } M4BUILTIN_HANDLER (pushdef) { const m4_call_info *me = m4_arg_info (argv); if (m4_is_arg_text (argv, 1)) { m4_symbol_value *value = m4_symbol_value_create (); if (m4_symbol_value_copy (context, value, m4_arg_symbol (argv, 2))) m4_warn (context, 0, me, _("cannot concatenate builtins")); m4_symbol_pushdef (M4SYMTAB, M4ARG (1), M4ARGLEN (1), value); } else m4_warn (context, 0, me, _("invalid macro name ignored")); } M4BUILTIN_HANDLER (popdef) { size_t i; for (i = 1; i < argc; i++) if (m4_symbol_value_lookup (context, argv, i, true)) m4_symbol_popdef (M4SYMTAB, M4ARG (i), M4ARGLEN (i)); } /* --- CONDITIONALS OF M4 --- */ M4BUILTIN_HANDLER (ifdef) { m4_push_arg (context, obs, argv, m4_symbol_value_lookup (context, argv, 1, false) ? 2 : 3); } M4BUILTIN_HANDLER (ifelse) { const m4_call_info *me = m4_arg_info (argv); size_t i; /* The valid ranges of argc for ifelse is discontinuous, we cannot rely on the regular mechanisms. */ if (argc == 2 || m4_bad_argc (context, argc, me, 3, -1, false)) return; else if (argc % 3 == 0) /* Diagnose excess arguments if 5, 8, 11, etc., actual arguments. */ m4_bad_argc (context, argc, me, 0, argc - 2, false); i = 1; argc--; while (true) { if (m4_arg_equal (context, argv, i, i + 1)) { m4_push_arg (context, obs, argv, i + 2); return; } switch (argc) { case 3: return; case 4: case 5: m4_push_arg (context, obs, argv, i + 3); return; default: argc -= 3; i += 3; } } } /* qsort comparison routine, for sorting the table made in m4_dumpdef (). */ static int dumpdef_cmp_CB (const void *s1, const void *s2) { const m4_string *a = (const m4_string *) s1; const m4_string *b = (const m4_string *) s2; return memcmp2 (a->str, a->len, b->str, b->len); } /* The function m4_dump_symbols () is for use by "dumpdef". It builds up a table of all defined symbol names. */ static void * dump_symbol_CB (m4_symbol_table *ignored M4_GNUC_UNUSED, const char *name, size_t len, m4_symbol *symbol, void *userdata) { m4_dump_symbol_data *symbol_data = (m4_dump_symbol_data *) userdata; m4_string *key; assert (name); assert (symbol); assert (!m4_is_symbol_value_void (m4_get_symbol_value (symbol))); if (symbol_data->size == 0) { char *base; size_t offset = obstack_object_size (symbol_data->obs); obstack_blank (symbol_data->obs, sizeof *symbol_data->base); symbol_data->size = (obstack_room (symbol_data->obs) / sizeof *symbol_data->base); base = (char *) obstack_base (symbol_data->obs) + offset; symbol_data->base = (m4_string *) base; } else { obstack_blank_fast (symbol_data->obs, sizeof *symbol_data->base); symbol_data->size--; } /* Safe to cast away const, since m4_dump_symbols adds it back. */ key = (m4_string *) symbol_data->base++; key->str = (char *) name; key->len = len; return NULL; } /* If there are no arguments, build a sorted list of all defined symbols, otherwise, only the specified symbols. */ void m4_dump_symbols (m4 *context, m4_dump_symbol_data *data, size_t argc, m4_macro_args *argv, bool complain) { assert (obstack_object_size (data->obs) == 0); data->size = obstack_room (data->obs) / sizeof *data->base; data->base = (m4_string *) obstack_base (data->obs); if (argc == 1) m4_symtab_apply (M4SYMTAB, false, dump_symbol_CB, data); else { size_t i; m4_symbol *symbol; for (i = 1; i < argc; i++) { symbol = m4_symbol_value_lookup (context, argv, i, complain); if (symbol) dump_symbol_CB (NULL, M4ARG (i), M4ARGLEN (i), symbol, data); } } data->size = obstack_object_size (data->obs) / sizeof *data->base; data->base = (m4_string *) obstack_finish (data->obs); /* Safe to cast away const, since we don't modify entries. */ qsort ((m4_string *) data->base, data->size, sizeof *data->base, dumpdef_cmp_CB); } /* Implementation of "dumpdef" itself. It builds up a table of pointers to symbols, sorts it and prints the sorted table. */ M4BUILTIN_HANDLER (dumpdef) { m4_dump_symbol_data data; const m4_string_pair *quotes = NULL; bool stack = m4_is_debug_bit (context, M4_DEBUG_TRACE_STACK); size_t arg_length = m4_get_max_debug_arg_length_opt (context); bool module = m4_is_debug_bit (context, M4_DEBUG_TRACE_MODULE); FILE *output = (m4_is_debug_bit (context, M4_DEBUG_TRACE_OUTPUT_DUMPDEF) ? stderr : m4_get_debug_file (context)); if (!output) return; if (m4_is_debug_bit (context, M4_DEBUG_TRACE_QUOTE)) quotes = m4_get_syntax_quotes (M4SYNTAX); data.obs = m4_arg_scratch (context); m4_dump_symbols (context, &data, argc, argv, true); m4_sysval_flush (context, false); for (; data.size > 0; --data.size, data.base++) { m4_symbol *symbol = m4_symbol_lookup (M4SYMTAB, data.base->str, data.base->len); char *value; size_t len; assert (symbol); /* TODO - add debugmode(b) option to control quoting style. */ obstack_grow (obs, data.base->str, data.base->len); obstack_1grow (obs, ':'); obstack_1grow (obs, '\t'); m4_symbol_print (context, symbol, obs, quotes, stack, arg_length, module); obstack_1grow (obs, '\n'); len = obstack_object_size (obs); value = (char *) obstack_finish (obs); fwrite (value, 1, len, output); obstack_free (obs, value); } } /* 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. */ M4BUILTIN_HANDLER (defn) { const m4_call_info *me = m4_arg_info (argv); size_t i; for (i = 1; i < argc; i++) { m4_symbol *symbol = m4_symbol_value_lookup (context, argv, i, true); if (!symbol) ; else if (m4_is_symbol_text (symbol)) m4_shipout_string (context, obs, m4_get_symbol_text (symbol), m4_get_symbol_len (symbol), true); else if (m4_is_symbol_func (symbol)) m4_push_builtin (context, obs, m4_get_symbol_value (symbol)); else if (m4_is_symbol_placeholder (symbol)) m4_warn (context, 0, me, _("%s: builtin %s requested by frozen file not found"), quotearg_n_mem (2, M4ARG (i), M4ARGLEN (i)), quotearg_style (locale_quoting_style, m4_get_symbol_placeholder (symbol))); else { assert (!"Bad token data type in m4_defn"); abort (); } } } /* This section contains macros to handle the builtins "syscmd" and "sysval". */ /* Exit code from last "syscmd" command. */ /* FIXME - we should preserve this value across freezing. See http://lists.gnu.org/archive/html/bug-m4/2006-06/msg00059.html for ideas on how do to that. */ static int m4_sysval = 0; void m4_set_sysval (int value) { m4_sysval = value; } /* Flush a given output STREAM. If REPORT, also print an error message and clear the stream error bit. */ static void sysval_flush_helper (m4 *context, FILE *stream, bool report) { if (fflush (stream) == EOF && report) { m4_error (context, 0, errno, NULL, _("write error")); clearerr (stream); } } /* Flush all user output streams, prior to doing something that can could lose unflushed data or interleave debug and normal output incorrectly. If REPORT, then print an error message on failure and clear the stream error bit; otherwise a subsequent ferror can track that an error occurred. */ void m4_sysval_flush (m4 *context, bool report) { FILE *debug_file = m4_get_debug_file (context); if (debug_file != stdout) sysval_flush_helper (context, stdout, report); if (debug_file != stderr) /* If we have problems with stderr, we can't really report that problem to stderr. The closeout module will ensure the exit status reflects the problem, though. */ fflush (stderr); if (debug_file != NULL) sysval_flush_helper (context, debug_file, report); /* POSIX requires that if m4 doesn't consume all input, but stdin is opened on a seekable file, that the file pointer be left at the next character on exit (but places no restrictions on the file pointer location on a non-seekable file). It also requires that fflush() followed by fseeko() on an input file set the underlying file pointer, and gnulib guarantees these semantics. However, fflush() on a non-seekable file can lose buffered data, which we might otherwise want to process after syscmd. Hence, we must check whether stdin is seekable. We must also be tolerant of operating with stdin closed, so we don't report any failures in this attempt. The stdio-safer module and friends are essential, so that if stdin was closed, this lseek is not on some other file that we have since opened. */ if (lseek (STDIN_FILENO, 0, SEEK_CUR) >= 0 && fflush (stdin) == 0) { fseeko (stdin, 0, SEEK_CUR); } } M4BUILTIN_HANDLER (syscmd) { const m4_call_info *me = m4_arg_info (argv); const char *cmd = M4ARG (1); size_t len = M4ARGLEN (1); int status; int sig_status; const char *prog_args[4] = { "sh", "-c" }; if (m4_get_safer_opt (context)) { m4_error (context, 0, 0, m4_arg_info (argv), _("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; errno = 0; status = execute (m4_info_name (me), M4_SYSCMD_SHELL, (char **) prog_args, false, false, false, false, true, false, &sig_status); if (sig_status) { assert (status == 127); m4_sysval = sig_status << 8; } else { if (status == 127 && errno) m4_warn (context, errno, me, _("cannot run command %s"), quotearg_style (locale_quoting_style, cmd)); m4_sysval = status; } } M4BUILTIN_HANDLER (sysval) { m4_shipout_int (obs, m4_sysval); } M4BUILTIN_HANDLER (incr) { int value; if (!m4_numeric_arg (context, m4_arg_info (argv), M4ARG (1), M4ARGLEN (1), &value)) return; m4_shipout_int (obs, value + 1); } M4BUILTIN_HANDLER (decr) { int value; if (!m4_numeric_arg (context, m4_arg_info (argv), M4ARG (1), M4ARGLEN (1), &value)) return; m4_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. */ M4BUILTIN_HANDLER (divert) { int i = 0; if (argc >= 2 && !m4_numeric_arg (context, m4_arg_info (argv), M4ARG (1), M4ARGLEN (1), &i)) return; m4_make_diversion (context, i); m4_divert_text (context, NULL, M4ARG (2), M4ARGLEN (2), m4_get_current_line (context)); } /* Expand to the current diversion number. */ M4BUILTIN_HANDLER (divnum) { m4_shipout_int (obs, m4_get_current_diversion (context)); } /* Bring back the diversion given by the argument list. If none is specified, bring back all diversions. GNU specific is the option of undiverting the named file, by passing a non-numeric argument to undivert (). */ M4BUILTIN_HANDLER (undivert) { size_t i = 0; const m4_call_info *me = m4_arg_info (argv); if (argc == 1) m4_undivert_all (context); else for (i = 1; i < argc; i++) { const char *str = M4ARG (i); size_t len = M4ARGLEN (i); char *endp; int diversion = strtol (str, &endp, 10); if (endp - str == len && !isspace ((unsigned char) *str)) m4_insert_diversion (context, diversion); else if (m4_get_posixly_correct_opt (context)) m4_warn (context, 0, me, _("non-numeric argument %s"), quotearg_style_mem (locale_quoting_style, str, len)); else if (strlen (str) != len) m4_warn (context, 0, me, _("invalid file name %s"), quotearg_style_mem (locale_quoting_style, str, len)); else { char *filepath = m4_path_search (context, str, NULL); FILE *fp = m4_fopen (context, filepath, "r"); free (filepath); if (fp != NULL) { m4_insert_file (context, fp); if (fclose (fp) == EOF) m4_error (context, 0, errno, me, _("error undiverting %s"), quotearg_style (locale_quoting_style, str)); } else m4_error (context, 0, errno, me, _("cannot undivert %s"), quotearg_style (locale_quoting_style, str)); } } } /* This section contains various macros, which does not fall into any specific group. These are "dnl", "shift", "changequote", "changecom" and "changesyntax" */ /* Delete all subsequent whitespace from input. The function skip_line () lives in input.c. */ M4BUILTIN_HANDLER (dnl) { m4_skip_line (context, m4_arg_info (argv)); } /* Shift all arguments one to the left, discarding the first argument. Each output argument is quoted with the current quotes. */ M4BUILTIN_HANDLER (shift) { m4_push_args (context, obs, argv, true, true); } /* Change the current quotes. The function set_quotes () lives in syntax.c. */ M4BUILTIN_HANDLER (changequote) { m4_set_quotes (M4SYNTAX, (argc >= 2) ? M4ARG (1) : NULL, M4ARGLEN (1), (argc >= 3) ? M4ARG (2) : NULL, M4ARGLEN (2)); } /* Change the current comment delimiters. The function set_comment () lives in syntax.c. */ M4BUILTIN_HANDLER (changecom) { m4_set_comment (M4SYNTAX, (argc >= 2) ? M4ARG (1) : NULL, M4ARGLEN (1), (argc >= 3) ? M4ARG (2) : NULL, M4ARGLEN (2)); } /* 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. */ static void include (m4 *context, m4_obstack *obs, size_t argc, m4_macro_args *argv, bool silent) { const m4_call_info *me = m4_arg_info (argv); const char *arg = M4ARG (1); size_t len = M4ARGLEN (1); if (strlen (arg) != len) m4_warn (context, 0, me, _("argument %s truncated"), quotearg_style_mem (locale_quoting_style, arg, len)); m4_load_filename (context, me, arg, obs, silent); } /* Include a file, complaining in case of errors. */ M4BUILTIN_HANDLER (include) { include (context, obs, argc, argv, false); } /* Include a file, ignoring errors. */ M4BUILTIN_HANDLER (sinclude) { include (context, obs, argc, argv, true); } /* More miscellaneous builtins -- "maketemp", "errprint". */ /* Add trailing `X' to PATTERN of length LEN as necessary, then securely create the temporary file system object. If DIR, create a directory instead of a file. Report errors on behalf of CALLER. If successful, output the quoted resulting name on OBS. */ void m4_make_temp (m4 *context, m4_obstack *obs, const m4_call_info *caller, const char *pattern, size_t len, bool dir) { int fd; int i; char *name; const m4_string_pair *quotes = m4_get_syntax_quotes (M4SYNTAX); if (m4_get_safer_opt (context)) { m4_error (context, 0, 0, caller, _("disabled by --safer")); return; } /* Guarantee that there are six trailing 'X' characters, even if the user forgot to supply them. Output must be quoted if successful. */ assert (obstack_object_size (obs) == 0); obstack_grow (obs, quotes->str1, quotes->len1); if (strlen (pattern) < len) { m4_warn (context, 0, caller, _("argument %s truncated"), quotearg_style_mem (locale_quoting_style, pattern, len)); len = strlen (pattern); } obstack_grow (obs, pattern, len); for (i = 0; len > 0 && i < 6; i++) if (pattern[--len] != 'X') break; obstack_grow0 (obs, "XXXXXX", 6 - i); name = (char *) obstack_base (obs) + quotes->len1; /* Make the temporary object. */ errno = 0; fd = gen_tempname (name, 0, 0, dir ? GT_DIR : GT_FILE); if (fd < 0) { /* This use of _() will need to change if xgettext ever changes its undocumented behavior of parsing both string options. */ m4_warn (context, errno, caller, _(dir ? "cannot create directory from template %s" : "cannot create file from template %s"), quotearg_style (locale_quoting_style, pattern)); obstack_free (obs, obstack_finish (obs)); } else { if (!dir) close (fd); /* Remove NUL, then finish quote. */ obstack_blank_fast (obs, -1); obstack_grow (obs, quotes->str2, quotes->len2); } } /* Use the first argument as at template for a temporary file name. */ M4BUILTIN_HANDLER (maketemp) { const m4_call_info *me = m4_arg_info (argv); m4_warn (context, 0, me, _("recommend using mkstemp instead")); if (m4_get_posixly_correct_opt (context)) { /* 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. 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 = M4ARG (1); size_t len = M4ARGLEN (1); size_t i; m4_obstack *scratch = m4_arg_scratch (context); size_t pid_len = obstack_printf (scratch, "%lu", (unsigned long) getpid ()); char *pid = (char *) obstack_copy0 (scratch, "", 0); for (i = len; i > 1; i--) if (str[i - 1] != 'X') break; obstack_grow (obs, str, i); if (len - i < pid_len) obstack_grow (obs, pid + pid_len - (len - i), len - i); else obstack_printf (obs, "%.*d%s", (int) (len - i - pid_len), 0, pid); } else m4_make_temp (context, obs, me, M4ARG (1), M4ARGLEN (1), false); } /* Use the first argument as a template for a temporary file name. */ M4BUILTIN_HANDLER (mkstemp) { m4_make_temp (context, obs, m4_arg_info (argv), M4ARG (1), M4ARGLEN (1), false); } /* Print all arguments on standard error. */ M4BUILTIN_HANDLER (errprint) { size_t i; m4_sysval_flush (context, false); /* The close_stdin module makes it safe to skip checking the return values here. */ fwrite (M4ARG (1), 1, M4ARGLEN (1), stderr); for (i = 2; i < m4_arg_argc (argv); i++) { fputc (' ', stderr); fwrite (M4ARG (i), 1, M4ARGLEN (i), stderr); } fflush (stderr); } /* 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 exitcode specified by the first argument, 0 if no arguments are present. */ M4BUILTIN_HANDLER (m4exit) { const m4_call_info *me = m4_arg_info (argv); int exit_code = EXIT_SUCCESS; /* Warn on bad arguments, but still exit. */ if (argc >= 2 && !m4_numeric_arg (context, me, M4ARG (1), M4ARGLEN (1), &exit_code)) exit_code = EXIT_FAILURE; if (exit_code < 0 || exit_code > 255) { m4_warn (context, 0, me, _("exit status out of range: `%d'"), exit_code); exit_code = EXIT_FAILURE; } /* Ensure that atexit handlers see correct nonzero status. */ if (exit_code != EXIT_SUCCESS) m4_set_exit_failure (exit_code); /* Change debug stream back to stderr, to force flushing debug stream and detect any errors. */ m4_debug_set_output (context, me, NULL); m4_sysval_flush (context, true); /* Check for saved error. */ if (exit_code == 0 && m4_get_exit_status (context) != 0) exit_code = m4_get_exit_status (context); 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. */ M4BUILTIN_HANDLER (m4wrap) { m4_wrap_args (context, argv); } /* 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. */ M4BUILTIN_HANDLER (traceon) { const m4_call_info *me = m4_arg_info (argv); size_t i; if (argc == 1) m4_set_debug_level_opt (context, (m4_get_debug_level_opt (context) | M4_DEBUG_TRACE_ALL)); else for (i = 1; i < argc; i++) if (m4_is_arg_text (argv, i)) m4_set_symbol_name_traced (M4SYMTAB, M4ARG (i), M4ARGLEN (i), true); else m4_warn (context, 0, me, _("invalid macro name ignored")); } /* Disable tracing of all specified macros, or all, if none is specified. */ M4BUILTIN_HANDLER (traceoff) { const m4_call_info *me = m4_arg_info (argv); size_t i; if (argc == 1) m4_set_debug_level_opt (context, (m4_get_debug_level_opt (context) & ~M4_DEBUG_TRACE_ALL)); else for (i = 1; i < argc; i++) if (m4_is_arg_text (argv, i)) m4_set_symbol_name_traced (M4SYMTAB, M4ARG (i), M4ARGLEN (i), false); else m4_warn (context, 0, me, _("invalid macro name ignored")); } /* 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. */ M4BUILTIN_HANDLER (len) { m4_shipout_int (obs, M4ARGLEN (1)); } /* The macro expands to the first index of the second argument in the first argument. As an extension, start the search at the index indicated by the third argument. */ M4BUILTIN_HANDLER (index) { const char *haystack = M4ARG (1); size_t haystack_len = M4ARGLEN (1); const char *needle = M4ARG (2); const char *result = NULL; int offset = 0; int retval = -1; if (!m4_arg_empty (argv, 3) && !m4_numeric_arg (context, m4_arg_info (argv), M4ARG (3), M4ARGLEN (3), &offset)) return; if (offset < 0) { offset += haystack_len; if (offset < 0) offset = 0; } else if (haystack_len < offset) { m4_shipout_int (obs, -1); return; } /* Rely on the optimizations guaranteed by gnulib's memmem module. */ result = (char *) memmem (haystack + offset, haystack_len - offset, needle, M4ARGLEN (2)); if (result) retval = result - haystack; m4_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 or empty, the substring extends to the end of the first argument. As an extension, negative arguments are treated as indices relative to the string length. Also, if a fourth argument is supplied, the original string is output with the selected substring replaced by the argument. */ M4BUILTIN_HANDLER (substr) { const m4_call_info *me = m4_arg_info (argv); const char *str = M4ARG (1); int start = 0; int end; int length; if (argc <= 2) { m4_push_arg (context, obs, argv, 1); return; } length = M4ARGLEN (1); if (!m4_arg_empty (argv, 2) && !m4_numeric_arg (context, me, M4ARG (2), M4ARGLEN (2), &start)) return; if (start < 0) start += length; if (m4_arg_empty (argv, 3)) end = length; else { if (!m4_numeric_arg (context, me, M4ARG (3), M4ARGLEN (3), &end)) return; if (end < 0) end += length; else end += start; } if (5 <= argc) { /* Replacement text provided. */ if (end < start) end = start; if (end < 0 || length < start) { m4_warn (context, 0, me, _("substring out of range")); return; } if (start < 0) start = 0; if (length < end) end = length; obstack_grow (obs, str, start); m4_push_arg (context, obs, argv, 4); obstack_grow (obs, str + end, length - end); return; } if (start < 0) start = 0; if (length < end) end = length; if (end <= start) return; obstack_grow (obs, str + start, end - start); } /* Any ranges in string S of length *LEN are expanded, using OBS for scratch space, and the expansion returned. *LEN is set to the expanded length. 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. */ const char * m4_expand_ranges (const char *s, size_t *len, m4_obstack *obs) { unsigned char from; unsigned char to; const char *end = s + *len; assert (obstack_object_size (obs) == 0); assert (s != end); from = *s++; obstack_1grow (obs, from); for ( ; s != end; from = *s++) { if (*s == '-') { if (++s == end) { /* trailing dash */ obstack_1grow (obs, '-'); break; } to = *s; if (from <= to) { while (from++ < to) obstack_1grow (obs, from); } else { while (--from >= to) obstack_1grow (obs, from); } } else obstack_1grow (obs, *s); } *len = obstack_object_size (obs); 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. */ M4BUILTIN_HANDLER (translit) { const char *data; const char *from; const char *to; size_t from_len; size_t to_len; char map[UCHAR_MAX + 1]; char found[UCHAR_MAX + 1]; unsigned char ch; enum { ASIS, REPLACE, DELETE }; if (m4_arg_empty (argv, 1) || m4_arg_empty (argv, 2)) { m4_push_arg (context, obs, argv, 1); return; } from = M4ARG (2); from_len = M4ARGLEN (2); to = M4ARG (3); to_len = M4ARGLEN (3); if (memchr (to, '-', to_len) != NULL) to = m4_expand_ranges (to, &to_len, m4_arg_scratch (context)); /* 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_len <= 2) { const char *p; size_t len = M4ARGLEN (1); int second = from[from_len / 2]; data = M4ARG (1); while ((p = (char *) memchr2 (data, from[0], second, len))) { obstack_grow (obs, data, p - data); len -= p - data + 1; data = p + 1; if (*p == from[0] && to_len) obstack_1grow (obs, to[0]); else if (*p == second && 1 < to_len) obstack_1grow (obs, to[1]); } obstack_grow (obs, data, len); return; } if (memchr (from, '-', from_len) != NULL) from = m4_expand_ranges (from, &from_len, m4_arg_scratch (context)); /* Calling memchr(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); while (from_len--) { ch = *from++; if (found[ch] == ASIS) { if (to_len) { found[ch] = REPLACE; map[ch] = *to; } else found[ch] = DELETE; } if (to_len) { to++; to_len--; } } data = M4ARG (1); from_len = M4ARGLEN (1); while (from_len--) { ch = *data++; switch (found[ch]) { case ASIS: obstack_1grow (obs, ch); break; case REPLACE: obstack_1grow (obs, map[ch]); break; case DELETE: break; default: assert (!"translit"); abort (); } } } /* The rest of this file contains the functions to evaluate integer * expressions for the "eval" macro. `number' should be at least 32 bits. */ #define numb_set(ans, x) ((ans) = (x)) #define numb_set_si(ans, si) (*(ans) = (number) (si)) #define numb_ZERO ((number) 0) #define numb_ONE ((number) 1) #define numb_init(x) ((x) = numb_ZERO) #define numb_fini(x) #define numb_incr(n) ((n) += numb_ONE) #define numb_decr(n) ((n) -= numb_ONE) #define numb_zerop(x) ((x) == numb_ZERO) #define numb_positivep(x) ((x) > numb_ZERO) #define numb_negativep(x) ((x) < numb_ZERO) #define numb_eq(x, y) ((x) = ((x) == (y))) #define numb_ne(x, y) ((x) = ((x) != (y))) #define numb_lt(x, y) ((x) = ((x) < (y))) #define numb_le(x, y) ((x) = ((x) <= (y))) #define numb_gt(x, y) ((x) = ((x) > (y))) #define numb_ge(x, y) ((x) = ((x) >= (y))) #define numb_lnot(x) ((x) = (!(x))) #define numb_lior(x, y) ((x) = ((x) || (y))) #define numb_land(x, y) ((x) = ((x) && (y))) #define numb_not(c, x) (*(x) = ~ *(x)) #define numb_eor(c, x, y) (*(x) = *(x) ^ *(y)) #define numb_ior(c, x, y) (*(x) = *(x) | *(y)) #define numb_and(c, x, y) (*(x) = *(x) & *(y)) #define numb_plus(x, y) ((x) = ((x) + (y))) #define numb_minus(x, y) ((x) = ((x) - (y))) #define numb_negate(x) ((x) = (- (x))) #define numb_times(x, y) ((x) = ((x) * (y))) /* Be careful of x86 SIGFPE. */ #define numb_ratio(x, y) \ (((y) == -1) ? (numb_negate (x)) : ((x) /= (y))) #define numb_divide(x, y) \ ((*(y) == -1) ? (numb_negate (*(y))) : (*(x) /= *(y))) #define numb_modulo(c, x, y) \ ((*(y) == -1) ? (*(x) = numb_ZERO) : (*(x) %= *(y))) /* numb_invert is only used in the context of x**-y, which integral math does not support. */ #define numb_invert(x) return NEGATIVE_EXPONENT /* Minimize undefined C behavior (shifting by a negative number, shifting by the width or greater, left shift overflow, or right shift of a negative number). Implement Java wrap-around semantics, with implicit masking of shift amount. This code assumes that the implementation-defined overflow when casting unsigned to signed is a silent twos-complement wrap-around. */ #define shift_mask (sizeof (number) * CHAR_BIT - 1) #define numb_lshift(c, x, y) \ (*(x) = (number) ((unumber) *(x) << (*(y) & shift_mask))) #define numb_rshift(c, x, y) \ (*(x) = (number) (*(x) < 0 \ ? ~(~(unumber) *(x) >> (*(y) & shift_mask)) \ : (unumber) *(x) >> (*(y) & shift_mask))) #define numb_urshift(c, x, y) \ (*(x) = (number) ((unumber) *(x) >> (*(y) & shift_mask))) /* The function ntoa () converts VALUE to a signed ASCII representation in radix RADIX. Radix must be between 2 and 36, inclusive. */ static const char * ntoa (number value, int radix) { /* Digits for number to ASCII conversions. */ static char const ntoa_digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; bool negative; unumber uvalue; /* Sized for radix 2, plus sign and trailing NUL. */ static char str[sizeof value * CHAR_BIT + 2]; char *s = &str[sizeof str]; *--s = '\0'; if (value < 0) { negative = true; uvalue = (unumber) -value; } else { negative = false; uvalue = (unumber) value; } do { *--s = ntoa_digits[uvalue % radix]; uvalue /= radix; } while (uvalue > 0); if (negative) *--s = '-'; return s; } static void numb_obstack (m4_obstack *obs, number value, int radix, int min) { const char *s; size_t len; unumber uvalue; if (radix == 1) { if (value < 0) { obstack_1grow (obs, '-'); uvalue = -value; } else uvalue = value; if (uvalue < min) { obstack_blank (obs, min - uvalue); memset ((char *) obstack_next_free (obs) - (min - uvalue), '0', min - uvalue); } obstack_blank (obs, uvalue); memset ((char *) obstack_next_free (obs) - uvalue, '1', uvalue); return; } s = ntoa (value, radix); if (*s == '-') { obstack_1grow (obs, '-'); s++; } len = strlen (s); if (len < min) { min -= len; obstack_blank (obs, min); memset ((char *) obstack_next_free (obs) - min, '0', min); } obstack_grow (obs, s, len); } static void numb_initialise (void) { ; } /* This macro defines the top level code for the "eval" builtin. The actual work is done in the function m4_evaluate (), which lives in evalparse.c. */ #define m4_evaluate builtin_eval #include "evalparse.c"