mirror of
https://https.git.savannah.gnu.org/git/m4.git
synced 2026-01-27 18:04:52 +00:00
* m4/m4module.h (m4_push_builtin): Add parameter. (m4_builtin_print, m4_push_wrapup_init, m4_push_wrapup_finish) (m4_arg_print, m4_symbol_value_print): Rename and reduce scope... * m4/m4private.h (m4__builtin_print, m4__push_wrapup_init) (m4__push_wrapup_finish, m4__arg_print, m4__symbol_value_print): ...to these, in some cases adding a parameter. (m4__append_builtin): New prototype. * m4/builtin.c (m4_builtin_print): Alter signature to print builtin to a growing symbol chain. * m4/symtab.c (m4__symbol_value_print): Alter signature. (m4_symbol_print, dump_symbol_CB): Adjust callers. * m4/input.c (builtin_peek, builtin_read, builtin_unget) (builtin_print, builtin_funcs): Delete, handled via composite blocks now. (struct m4_input_block): Delete u.builtin member. (init_builtin_token): Only use composite block. (m4__append_builtin): New function. (m4_push_builtin, m4__push_wrapup_init): Alter signature. (m4__push_symbol): Allow builtin tokens. (m4__push_wrapup_finish): Rename. (composite_print, m4_print_token): Adjust callers. * m4/macro.c (m4_wrap_args, collect_arguments): Allow builtin tokens. (m4__arg_print): Alter signature. (trace_prepre, trace_pre, m4_arg_text, m4_arg_equal): Adjust callers. * modules/m4.c (m4wrap): Allow builtin tokens. (defn, errprint): Adjust callers. * modules/gnu.c (builtin): Likewise. * doc/m4.texinfo (M4wrap): New test. (Debuglen): Adjust expected output. Signed-off-by: Eric Blake <ebb9@byu.net>
941 lines
25 KiB
C
941 lines
25 KiB
C
/* GNU m4 -- A simple macro processor
|
||
Copyright (C) 2000, 2004, 2005, 2006, 2007, 2008 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"
|
||
|
||
/* Rename exported symbols for dlpreload()ing. */
|
||
#define m4_builtin_table gnu_LTX_m4_builtin_table
|
||
#define m4_macro_table gnu_LTX_m4_macro_table
|
||
|
||
|
||
/* 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 (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. */
|
||
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. */
|
||
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 },
|
||
};
|
||
|
||
|
||
|
||
/* 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 char *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_error (context, 0, 0, caller, _("bad regular expression `%s': %s"),
|
||
regexp, 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, 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 char *caller,
|
||
const char *victim, const char *repl, m4_pattern_buffer *buf)
|
||
{
|
||
int ch;
|
||
|
||
for (;;)
|
||
{
|
||
while ((ch = *repl++) != '\\')
|
||
{
|
||
if (ch == '\0')
|
||
return;
|
||
obstack_1grow (obs, ch);
|
||
}
|
||
|
||
switch ((ch = *repl++))
|
||
{
|
||
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;
|
||
|
||
case '\0':
|
||
m4_warn (context, 0, caller,
|
||
_("trailing \\ ignored in replacement"));
|
||
return;
|
||
|
||
default:
|
||
obstack_1grow (obs, ch);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/* For each match against compiled REGEXP (held in BUF -- as returned
|
||
by regexp_compile) in VICTIM, substitute REPLACE. 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 char *caller,
|
||
const char *victim, size_t len, const char *regexp,
|
||
m4_pattern_buffer *buf, const char *replace,
|
||
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,
|
||
_("error matching regular expression `%s'"), regexp);
|
||
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, 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;
|
||
}
|
||
|
||
|
||
/* Reclaim memory used by this module. */
|
||
M4FINISH_HANDLER(gnu)
|
||
{
|
||
int i;
|
||
for (i = 0; i < REGEX_CACHE_SIZE; i++)
|
||
if (regex_cache[i].str)
|
||
{
|
||
free (regex_cache[i].str);
|
||
regfree (regex_cache[i].pat);
|
||
free (regex_cache[i].pat);
|
||
free (regex_cache[i].regs.start);
|
||
free (regex_cache[i].regs.end);
|
||
}
|
||
/* If this module was preloaded, then we need to explicitly reset
|
||
the memory in case it gets reloaded. */
|
||
memset (®ex_cache, 0, sizeof regex_cache);
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* __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 char *me = M4ARG (0);
|
||
const char *name;
|
||
m4_symbol_value *value;
|
||
|
||
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);
|
||
value = m4_builtin_find_by_name (NULL, name);
|
||
if (value == NULL)
|
||
m4_warn (context, 0, me, _("undefined builtin `%s'"), name);
|
||
else
|
||
{
|
||
m4_push_builtin (context, obs, value);
|
||
free (value);
|
||
}
|
||
}
|
||
else
|
||
m4_warn (context, 0, me, _("invalid macro name ignored"));
|
||
}
|
||
else
|
||
{
|
||
name = M4ARG (1);
|
||
value = m4_builtin_find_by_name (NULL, name);
|
||
if (value == NULL)
|
||
m4_warn (context, 0, me, _("undefined builtin `%s'"), name);
|
||
else
|
||
{
|
||
const m4_builtin *bp = m4_get_symbol_value_builtin (value);
|
||
if (!m4_bad_argc (context, argc - 1, name,
|
||
bp->min_args, bp->max_args,
|
||
(bp->flags & M4_BUILTIN_SIDE_EFFECT) != 0))
|
||
{
|
||
m4_macro_args *new_argv;
|
||
bool flatten = (bp->flags & M4_BUILTIN_FLATTEN_ARGS) != 0;
|
||
new_argv = m4_make_argv_ref (context, argv, name, M4ARGLEN (1),
|
||
true, flatten);
|
||
bp->func (context, obs, argc - 1, new_argv);
|
||
}
|
||
free (value);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/* Change the current regexp syntax. Currently this affects the
|
||
builtins: `patsubst', `regexp' and `renamesyms'. */
|
||
|
||
static int
|
||
m4_resyntax_encode_safe (m4 *context, const char *caller, const char *spec)
|
||
{
|
||
int resyntax = m4_regexp_syntax_encode (spec);
|
||
|
||
if (resyntax < 0)
|
||
m4_warn (context, 0, caller, _("bad syntax-spec: `%s'"), spec);
|
||
|
||
return resyntax;
|
||
}
|
||
|
||
|
||
/**
|
||
* changeresyntax(RESYNTAX-SPEC)
|
||
**/
|
||
M4BUILTIN_HANDLER (changeresyntax)
|
||
{
|
||
int resyntax = m4_resyntax_encode_safe (context, M4ARG (0), M4ARG (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 char *me = M4ARG (0);
|
||
M4_MODULE_IMPORT (m4, m4_expand_ranges);
|
||
|
||
if (m4_expand_ranges)
|
||
{
|
||
size_t i;
|
||
for (i = 1; i < argc; i++)
|
||
{
|
||
const char *spec = M4ARG (i);
|
||
char key = *spec++;
|
||
char action = key ? *spec : '\0';
|
||
switch (action)
|
||
{
|
||
case '-':
|
||
case '+':
|
||
case '=':
|
||
spec++;
|
||
break;
|
||
case '\0':
|
||
break;
|
||
default:
|
||
action = '=';
|
||
break;
|
||
}
|
||
if (m4_set_syntax (M4SYNTAX, key, action,
|
||
key ? m4_expand_ranges (spec, obs) : "") < 0)
|
||
m4_warn (context, 0, me, _("undefined syntax code: `%c'"),
|
||
key);
|
||
}
|
||
}
|
||
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 char *me = M4ARG (0);
|
||
|
||
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 if (!m4_debug_set_output (context, me, M4ARG (1)))
|
||
m4_error (context, 0, errno, me, _("cannot set debug file `%s'"),
|
||
M4ARG (1));
|
||
}
|
||
|
||
|
||
/* 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, M4ARG (0), M4ARG (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)
|
||
{
|
||
int debug_level = m4_get_debug_level_opt (context);
|
||
int new_debug_level;
|
||
|
||
if (argc == 1)
|
||
m4_set_debug_level_opt (context, 0);
|
||
else
|
||
{
|
||
new_debug_level = m4_debug_decode (context, debug_level, M4ARG (1));
|
||
if (new_debug_level < 0)
|
||
m4_error (context, 0, 0, M4ARG (0), _("bad debug flags: `%s'"),
|
||
M4ARG (1));
|
||
else
|
||
m4_set_debug_level_opt (context, new_debug_level);
|
||
}
|
||
}
|
||
|
||
|
||
/* Same as the sysymd builtin from m4.c module, but expand to the
|
||
output of SHELL-COMMAND. */
|
||
|
||
/**
|
||
* esyscmd(SHELL-COMMAND)
|
||
**/
|
||
|
||
M4BUILTIN_HANDLER (esyscmd)
|
||
{
|
||
const char *me = M4ARG (0);
|
||
M4_MODULE_IMPORT (m4, m4_set_sysval);
|
||
M4_MODULE_IMPORT (m4, m4_sysval_flush);
|
||
|
||
if (m4_set_sysval && m4_sysval_flush)
|
||
{
|
||
FILE *pin;
|
||
int ch;
|
||
|
||
if (m4_get_safer_opt (context))
|
||
{
|
||
m4_error (context, 0, 0, me, _("disabled by --safer"));
|
||
return;
|
||
}
|
||
|
||
/* Optimize the empty command. */
|
||
if (m4_arg_empty (argv, 1))
|
||
{
|
||
m4_set_sysval (0);
|
||
return;
|
||
}
|
||
|
||
m4_sysval_flush (context, false);
|
||
errno = 0;
|
||
pin = popen (M4ARG (1), "r");
|
||
if (pin == NULL)
|
||
{
|
||
m4_error (context, 0, errno, me,
|
||
_("cannot open pipe to command `%s'"), M4ARG (1));
|
||
m4_set_sysval (-1);
|
||
}
|
||
else
|
||
{
|
||
while ((ch = getc (pin)) != EOF)
|
||
obstack_1grow (obs, ch);
|
||
m4_set_sysval (pclose (pin));
|
||
}
|
||
}
|
||
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 char *me = M4ARG (0);
|
||
if (!m4_is_arg_text (argv, 1))
|
||
m4_warn (context, 0, me, _("invalid macro name ignored"));
|
||
else
|
||
{
|
||
const char *name = M4ARG (1);
|
||
m4_symbol *symbol = m4_symbol_lookup (M4SYMTAB, name);
|
||
|
||
if (symbol == NULL)
|
||
m4_warn (context, 0, me, _("undefined macro `%s'"), name);
|
||
else
|
||
{
|
||
m4_macro_args *new_argv;
|
||
new_argv = m4_make_argv_ref (context, argv, name, M4ARGLEN (1),
|
||
true, m4_symbol_flatten_args (symbol));
|
||
m4_macro_call (context, m4_get_symbol_value (symbol), obs,
|
||
argc - 1, 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, M4ARG (0), 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 char *me; /* name of this macro */
|
||
const char *pattern; /* regular expression */
|
||
const char *replace; /* replacement */
|
||
m4_pattern_buffer *buf; /* compiled regular expression */
|
||
int resyntax;
|
||
|
||
me = M4ARG (0);
|
||
pattern = M4ARG (2);
|
||
replace = M4ARG (3);
|
||
|
||
resyntax = m4_get_regexp_syntax_opt (context);
|
||
if (argc >= 5) /* additional args ignored */
|
||
{
|
||
resyntax = m4_resyntax_encode_safe (context, me, M4ARG (4));
|
||
if (resyntax < 0)
|
||
return;
|
||
}
|
||
|
||
/* The empty regex matches everywhere, but if there is no
|
||
replacement, we need not waste time with it. */
|
||
if (!*pattern && !*replace)
|
||
{
|
||
m4_push_arg (context, obs, argv, 1);
|
||
return;
|
||
}
|
||
|
||
buf = regexp_compile (context, me, pattern, M4ARGLEN (2), resyntax);
|
||
if (!buf)
|
||
return;
|
||
|
||
regexp_substitute (context, obs, me, M4ARG (1), M4ARGLEN (1),
|
||
pattern, buf, replace, 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 char *me; /* name of this macro */
|
||
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;
|
||
|
||
me = M4ARG (0);
|
||
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 ((*replace == '\0') || (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));
|
||
if (resyntax < 0)
|
||
return;
|
||
}
|
||
else
|
||
/* regexp(VICTIM, REGEXP) */
|
||
replace = NULL;
|
||
|
||
if (!*pattern)
|
||
{
|
||
/* The empty regex matches everything. */
|
||
if (replace)
|
||
substitute (context, obs, me, M4ARG (1), replace, 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, _("error matching regular expression `%s'"),
|
||
pattern);
|
||
return;
|
||
}
|
||
|
||
if (replace == NULL)
|
||
m4_shipout_int (obs, startpos);
|
||
else if (startpos >= 0)
|
||
substitute (context, obs, me, victim, replace, 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 char *me; /* name of this macro */
|
||
const char *regexp; /* regular expression string */
|
||
const char *replace; /* replacement expression string */
|
||
|
||
m4_pattern_buffer *buf; /* compiled regular expression */
|
||
|
||
m4_dump_symbol_data data;
|
||
|
||
int resyntax;
|
||
|
||
me = M4ARG (0);
|
||
regexp = M4ARG (1);
|
||
replace = M4ARG (2);
|
||
|
||
resyntax = m4_get_regexp_syntax_opt (context);
|
||
if (argc >= 4)
|
||
{
|
||
resyntax = m4_resyntax_encode_safe (context, me, M4ARG (3));
|
||
if (resyntax < 0)
|
||
return;
|
||
}
|
||
|
||
buf = regexp_compile (context, me, regexp, M4ARGLEN (1), 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 char *name = data.base[0];
|
||
|
||
if (regexp_substitute (context, data.obs, me, name, strlen (name),
|
||
regexp, buf, replace, true))
|
||
{
|
||
obstack_1grow (data.obs, '\0');
|
||
m4_symbol_rename (M4SYMTAB, name,
|
||
(char *) obstack_finish (data.obs));
|
||
}
|
||
}
|
||
}
|
||
else
|
||
assert (!"Unable to import from m4 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[0], SIZE_MAX, 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, M4ARG (1), M4ARG (0), value);
|
||
m4_set_syncoutput_opt (context, value);
|
||
}
|