blob: dd67f8570aba45c6991a00a027698ba511b4a1a7 [file] [log] [blame]
/* GNU m4 -- A simple macro processor
Copyright (C) 1989, 1990, 1991, 1992, 1993, 1994, 2000, 2004, 2006,
2007, 2008, 2009 Free Software Foundation, Inc.
This file is part of GNU M4.
GNU M4 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
GNU M4 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* Code for all builtin macros, initialization of symbol table, and
expansion of user defined macros. */
#include "m4.h"
#include "regex.h"
#if HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
/* Initialization of builtin and predefined macros. The table
"builtin_tab" is both used for initialization, and by the "builtin"
builtin. */
#define DECLARE(name) \
static void name (struct obstack *, int, macro_arguments *)
DECLARE (m4___file__);
DECLARE (m4___line__);
DECLARE (m4___program__);
DECLARE (m4_builtin);
DECLARE (m4_changecom);
DECLARE (m4_changequote);
#ifdef ENABLE_CHANGEWORD
DECLARE (m4_changeword);
#endif
DECLARE (m4_debugmode);
DECLARE (m4_debugfile);
DECLARE (m4_decr);
DECLARE (m4_define);
DECLARE (m4_defn);
DECLARE (m4_divert);
DECLARE (m4_divnum);
DECLARE (m4_dnl);
DECLARE (m4_dumpdef);
DECLARE (m4_errprint);
DECLARE (m4_esyscmd);
DECLARE (m4_eval);
DECLARE (m4_format);
DECLARE (m4_ifdef);
DECLARE (m4_ifelse);
DECLARE (m4_include);
DECLARE (m4_incr);
DECLARE (m4_index);
DECLARE (m4_indir);
DECLARE (m4_len);
DECLARE (m4_m4exit);
DECLARE (m4_m4wrap);
DECLARE (m4_maketemp);
DECLARE (m4_mkstemp);
DECLARE (m4_patsubst);
DECLARE (m4_popdef);
DECLARE (m4_pushdef);
DECLARE (m4_qindir);
DECLARE (m4_regexp);
DECLARE (m4_shift);
DECLARE (m4_sinclude);
DECLARE (m4_substr);
DECLARE (m4_syscmd);
DECLARE (m4_sysval);
DECLARE (m4_traceoff);
DECLARE (m4_traceon);
DECLARE (m4_translit);
DECLARE (m4_undefine);
DECLARE (m4_undivert);
#undef DECLARE
static builtin
builtin_tab[] =
{
/* name GNUext macros blind function */
{ "__file__", true, false, false, m4___file__ },
{ "__line__", true, false, false, m4___line__ },
{ "__program__", true, false, false, m4___program__ },
{ "builtin", true, true, true, m4_builtin },
{ "changecom", false, false, false, m4_changecom },
{ "changequote", false, false, false, m4_changequote },
#ifdef ENABLE_CHANGEWORD
{ "changeword", true, false, true, m4_changeword },
#endif
{ "debugmode", true, false, false, m4_debugmode },
{ "debugfile", true, false, false, m4_debugfile },
{ "decr", false, false, true, m4_decr },
{ "define", false, true, true, m4_define },
{ "defn", false, true, true, m4_defn },
{ "divert", false, false, false, m4_divert },
{ "divnum", false, false, false, m4_divnum },
{ "dnl", false, false, false, m4_dnl },
{ "dumpdef", false, true, false, m4_dumpdef },
{ "errprint", false, false, true, m4_errprint },
{ "esyscmd", true, false, true, m4_esyscmd },
{ "eval", false, false, true, m4_eval },
{ "format", true, false, true, m4_format },
{ "ifdef", false, true, true, m4_ifdef },
{ "ifelse", false, true, true, m4_ifelse },
{ "include", false, false, true, m4_include },
{ "incr", false, false, true, m4_incr },
{ "index", false, false, true, m4_index },
{ "indir", true, true, true, m4_indir },
{ "len", false, false, true, m4_len },
{ "m4exit", false, false, false, m4_m4exit },
{ "m4wrap", false, true, true, m4_m4wrap },
{ "maketemp", false, false, true, m4_maketemp },
{ "mkstemp", false, false, true, m4_mkstemp },
{ "patsubst", true, false, true, m4_patsubst },
{ "popdef", false, true, true, m4_popdef },
{ "pushdef", false, true, true, m4_pushdef },
{ "qindir", true, true, true, m4_qindir },
{ "regexp", true, false, true, m4_regexp },
{ "shift", false, true, true, m4_shift },
{ "sinclude", false, false, true, m4_sinclude },
{ "substr", false, false, true, m4_substr },
{ "syscmd", false, false, true, m4_syscmd },
{ "sysval", false, false, false, m4_sysval },
{ "traceoff", false, true, false, m4_traceoff },
{ "traceon", false, true, false, m4_traceon },
{ "translit", false, false, true, m4_translit },
{ "undefine", false, true, true, m4_undefine },
{ "undivert", false, false, false, m4_undivert },
{ 0, false, false, false, 0 },
/* placeholder is intentionally stuck after the table end delimiter,
so that we can easily find it, while not treating it as a real
builtin. */
{ "placeholder", true, false, false, m4_placeholder },
};
static predefined const
predefined_tab[] =
{
#if UNIX
{ "unix", "__unix__", "" },
#elif W32_NATIVE
{ "windows", "__windows__", "" },
#elif OS2
{ "os2", "__os2__", "" },
#else
# warning Platform macro not provided
#endif
{ NULL, "__gnu__", "" },
{ NULL, NULL, NULL },
};
/*----------------------------------------.
| Find the builtin, which lives on ADDR. |
`----------------------------------------*/
const builtin *
find_builtin_by_addr (builtin_func *func)
{
const builtin *bp;
for (bp = &builtin_tab[0]; bp->name != NULL; bp++)
if (bp->func == func)
return bp;
if (func == m4_placeholder)
return bp + 1;
return NULL;
}
/*----------------------------------------------------------.
| Find the builtin, which has NAME. On failure, return the |
| placeholder builtin. |
`----------------------------------------------------------*/
const builtin *
find_builtin_by_name (const char *name)
{
const builtin *bp;
for (bp = &builtin_tab[0]; bp->name != NULL; bp++)
if (strcmp (bp->name, name) == 0)
return bp;
return bp + 1;
}
/*------------------------------------------------------------------.
| Print a representation of FUNC to OBS. If FLATTEN, output QUOTES |
| around an empty string instead; else if CHAIN, append the builtin |
| to the chain; otherwise print the name of FUNC. |
`------------------------------------------------------------------*/
void
func_print (struct obstack *obs, const builtin *func, bool flatten,
token_chain **chain, const string_pair *quotes)
{
assert (func);
if (flatten)
{
if (quotes)
{
obstack_grow (obs, quotes->str1, quotes->len1);
obstack_grow (obs, quotes->str2, quotes->len2);
}
}
else if (chain)
append_macro (obs, func->func, NULL, chain);
else
{
obstack_1grow (obs, '<');
obstack_grow (obs, func->name, strlen (func->name));
obstack_1grow (obs, '>');
}
}
/*----------------------------------------------------------------.
| Install a builtin macro with name NAME and length LEN, bound to |
| the C function given in BP. MODE is SYMBOL_INSERT or |
| SYMBOL_PUSHDEF. |
`----------------------------------------------------------------*/
void
define_builtin (const char *name, size_t len, const builtin *bp,
symbol_lookup mode)
{
symbol *sym;
sym = lookup_symbol (name, len, mode);
SYMBOL_TYPE (sym) = TOKEN_FUNC;
SYMBOL_MACRO_ARGS (sym) = bp->groks_macro_args;
SYMBOL_BLIND_NO_ARGS (sym) = bp->blind_if_no_args;
SYMBOL_FUNC (sym) = bp->func;
}
/* Storage for the compiled regular expression of
--warn-macro-sequence. */
static struct re_pattern_buffer macro_sequence_buf;
/* Storage for the matches of --warn-macro-sequence. */
static struct re_registers macro_sequence_regs;
/* True if --warn-macro-sequence is in effect. */
static bool macro_sequence_inuse;
/* 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 caching compiled regex. */
struct m4_regex {
unsigned count; /* usage counter */
size_t len; /* length of string */
char *str; /* copy of compiled string */
struct re_pattern_buffer *buf; /* compiled regex, allocated */
struct re_registers regs; /* match registers, reused */
};
typedef struct m4_regex m4_regex;
/* Storage for the cache of regular expressions. */
static m4_regex regex_cache[REGEX_CACHE_SIZE];
#ifdef DEBUG_REGEX
extern FILE *trace_file;
#endif /* DEBUG_REGEX */
/*------------------------------------------------------------------.
| Compile STR, with length LEN, into a regex. On success, set BUF |
| and REGS to the compiled regex. Compilation is cached, so do not |
| free the results here; rather, use free_regex at the end of the |
| program. Return NULL on success, or an error message. |
`------------------------------------------------------------------*/
static const char *
compile_pattern (const char *str, size_t len, struct re_pattern_buffer **buf,
struct re_registers **regs)
{
int i;
m4_regex *victim;
unsigned victim_count;
struct re_pattern_buffer *new_buf;
struct re_registers *new_regs;
const char *msg;
/* First, check if STR is already cached. If so, increase its use
count and return it. */
for (i = 0; i < REGEX_CACHE_SIZE; i++)
if (len == regex_cache[i].len && regex_cache[i].str
&& memcmp (str, regex_cache[i].str, len) == 0)
{
*buf = regex_cache[i].buf;
*regs = &regex_cache[i].regs;
regex_cache[i].count++;
#ifdef DEBUG_REGEX
if (trace_file)
{
fputs ("cached:{", trace_file);
fwrite (str, 1, len, trace_file);
fputs ("}\n", trace_file);
}
#endif /* DEBUG_REGEX */
return NULL;
}
/* Next, check if STR can be compiled. */
new_buf = (struct re_pattern_buffer *) xzalloc (sizeof *new_buf);
msg = re_compile_pattern (str, len, new_buf);
#ifdef DEBUG_REGEX
if (trace_file)
{
fputs ("compile:{", trace_file);
fwrite (str, 1, len, trace_file);
fputs ("}\n", trace_file);
}
#endif /* DEBUG_REGEX */
if (msg)
{
regfree (new_buf);
free (new_buf);
return msg;
}
/* Use a fastmap for speed; it is freed by regfree. */
new_buf->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 = &regex_cache[i];
}
if (regex_cache[i].count)
regex_cache[i].count--;
}
victim->count = REGEX_CACHE_SIZE;
victim->len = len;
if (victim->str)
{
#ifdef DEBUG_REGEX
if (trace_file)
{
fputs ("flush:{", trace_file);
fwrite (victim->str, 1, victim->len, trace_file);
fputs ("}\n", trace_file);
}
#endif /* DEBUG_REGEX */
free (victim->str);
regfree (victim->buf);
free (victim->buf);
}
victim->str = xstrdup (str);
victim->buf = new_buf;
new_regs = &victim->regs;
re_set_registers (new_buf, new_regs, new_regs->num_regs,
new_regs->start, new_regs->end);
*buf = new_buf;
*regs = new_regs;
return NULL;
}
/*----------------------------------------.
| Clean up regular expression variables. |
`----------------------------------------*/
static void
free_pattern_buffer (struct re_pattern_buffer *buf, struct re_registers *regs)
{
regfree (buf);
free (regs->start);
free (regs->end);
}
/*-----------------------------------------------------------------.
| Set the regular expression of --warn-macro-sequence that will be |
| checked during define and pushdef. Exit on failure. |
`-----------------------------------------------------------------*/
void
set_macro_sequence (const char *regexp)
{
const char *msg;
if (!regexp)
regexp = DEFAULT_MACRO_SEQUENCE;
else if (regexp[0] == '\0')
{
macro_sequence_inuse = false;
return;
}
msg = re_compile_pattern (regexp, strlen (regexp), &macro_sequence_buf);
if (msg != NULL)
m4_error (EXIT_FAILURE, 0, NULL,
_("--warn-macro-sequence: bad regular expression %s: %s"),
quotearg_style (locale_quoting_style, regexp), msg);
re_set_registers (&macro_sequence_buf, &macro_sequence_regs,
macro_sequence_regs.num_regs,
macro_sequence_regs.start, macro_sequence_regs.end);
macro_sequence_inuse = true;
}
/*----------------------------------------------------------------.
| Check the contents DEFN of length LEN, of the new definition of |
| macro NAME of length NAME_LEN, for any match of |
| --warn-macro-sequence. |
`----------------------------------------------------------------*/
static void
check_macro_sequence (const char *name, size_t name_len, const char *defn,
size_t len)
{
if (macro_sequence_inuse && len)
{
regoff_t offset = 0;
struct re_registers *regs = &macro_sequence_regs;
while (offset < len
&& (offset = re_search (&macro_sequence_buf, defn, len, offset,
len - offset, regs)) >= 0)
{
/* Skip empty matches. */
if (regs->start[0] == regs->end[0])
offset++;
else
{
offset = regs->end[0];
/* Safe to use slot 1 since we don't pass a macro name
to m4_warn. */
m4_warn (0, NULL, _("definition of %s contains sequence %s"),
quotearg_style_mem (locale_quoting_style, name,
name_len),
quotearg_n_style_mem (1, locale_quoting_style,
defn + regs->start[0],
regs->end[0] - regs->start[0]));
}
}
if (offset == -2)
m4_warn (0, NULL,
_("problem checking --warn-macro-sequence for macro %s"),
quotearg_style_mem (locale_quoting_style, name, name_len));
}
}
/*------------------------------------------------------.
| Free dynamic memory utilized by regular expressions. |
`------------------------------------------------------*/
void
free_regex (void)
{
int i;
free_pattern_buffer (&macro_sequence_buf, &macro_sequence_regs);
for (i = 0; i < REGEX_CACHE_SIZE; i++)
if (regex_cache[i].str)
{
free (regex_cache[i].str);
free_pattern_buffer (regex_cache[i].buf, &regex_cache[i].regs);
free (regex_cache[i].buf);
}
}
/*-----------------------------------------------------------------.
| Define a predefined or user-defined macro, with name NAME of |
| length NAME_LEN, and expansion TEXT of length LEN. LEN may be |
| SIZE_MAX, to use the string length of TEXT instead. QUOTE_AGE |
| describes the quoting used by TEXT, or zero to force rescanning |
| when defn is later used to retrieve TEXT. MODE is SYMBOL_INSERT |
| for "define" or SYMBOL_PUSHDEF for "pushdef". This function is |
| also used from main (). |
`-----------------------------------------------------------------*/
void
define_user_macro (const char *name, size_t name_len, const char *text,
size_t len, symbol_lookup mode, unsigned int quote_age)
{
symbol *s;
char *defn;
assert (text);
if (len == SIZE_MAX)
len = strlen (text);
defn = xmemdup (text, len);
s = lookup_symbol (name, name_len, mode);
if (SYMBOL_TYPE (s) == TOKEN_TEXT)
free (SYMBOL_TEXT (s));
SYMBOL_TYPE (s) = TOKEN_TEXT;
SYMBOL_TEXT (s) = defn;
SYMBOL_TEXT_LEN (s) = len;
SYMBOL_TEXT_ALLOCATED (s) = len;
SYMBOL_TEXT_QUOTE_AGE (s) = quote_age;
SYMBOL_MACRO_ARGS (s) = true;
check_macro_sequence (name, name_len, defn, len);
}
/*-----------------------------------------------.
| Initialize all builtin and predefined macros. |
`-----------------------------------------------*/
void
builtin_init (void)
{
const builtin *bp;
const predefined *pp;
char *string;
for (bp = &builtin_tab[0]; bp->name != NULL; bp++)
if (!no_gnu_extensions || !bp->gnu_extension)
{
size_t len = strlen (bp->name);
if (prefix_all_builtins)
{
string = xasprintf ("m4_%s", bp->name);
define_builtin (string, len + 3, bp, SYMBOL_INSERT);
free (string);
}
else
define_builtin (bp->name, len, bp, SYMBOL_INSERT);
}
for (pp = &predefined_tab[0]; pp->func != NULL; pp++)
if (no_gnu_extensions)
{
if (pp->unix_name != NULL)
define_user_macro (pp->unix_name, strlen (pp->unix_name),
pp->func, SIZE_MAX, SYMBOL_INSERT, 0);
}
else
{
if (pp->gnu_name != NULL)
define_user_macro (pp->gnu_name, strlen (pp->gnu_name),
pp->func, SIZE_MAX, SYMBOL_INSERT, 0);
}
}
/*------------------------------------------------------------------.
| Give friendly warnings if a builtin macro is passed an |
| inappropriate number of arguments. NAME is macro name for |
| messages. ARGC is one more than the number of arguments. MIN is |
| the 0-based minimum number of acceptable arguments. MAX is the |
| 0-based maximum number of arguments, UINT_MAX if not applicable. |
| Return true if there are not enough arguments. |
`------------------------------------------------------------------*/
bool
bad_argc (const call_info *name, int argc, unsigned int min, unsigned int max)
{
if (argc - 1 < min)
{
m4_warn (0, name, _("too few arguments: %d < %d"), argc - 1, min);
return true;
}
if (argc - 1 > max)
m4_warn (0, name, _("extra arguments ignored: %d > %d"), argc - 1, max);
return false;
}
/*------------------------------------------------------------------.
| The function numeric_arg () converts ARG of length LEN to an int |
| pointed to by VALUEP. If the conversion fails, print error |
| message on behalf of NAME. Return true iff conversion succeeds. |
`------------------------------------------------------------------*/
static bool
numeric_arg (const call_info *name, const char *arg, size_t len, int *valuep)
{
char *endp;
if (!len)
{
*valuep = 0;
m4_warn (0, name, _("empty string treated as 0"));
}
else
{
errno = 0;
*valuep = strtol (arg, &endp, 10);
if (endp - arg != len)
{
m4_warn (0, name, _("non-numeric argument %s"),
quotearg_style_mem (locale_quoting_style, arg, len));
return false;
}
if (isspace (to_uchar (*arg)))
m4_warn (0, name, _("leading whitespace ignored"));
else if (errno == ERANGE)
m4_warn (0, name, _("numeric overflow detected"));
}
return true;
}
/*------------------------------------------------------------------------.
| The function ntoa () converts VALUE to a signed ascii representation in |
| radix RADIX. |
`------------------------------------------------------------------------*/
/* Digits for number to ascii conversions. */
static char const digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
static const char *
ntoa (int32_t value, int radix)
{
bool negative;
uint32_t 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 = -(uint32_t) value;
}
else
{
negative = false;
uvalue = (uint32_t) value;
}
do
{
*--s = digits[uvalue % radix];
uvalue /= radix;
}
while (uvalue > 0);
if (negative)
*--s = '-';
return s;
}
/*----------------------------------------------------------------------.
| Format an int VAL, and stuff it into an obstack OBS. Used for macros |
| expanding to numbers. |
`----------------------------------------------------------------------*/
static void
shipout_int (struct obstack *obs, int val)
{
const char *s;
s = ntoa ((int32_t) val, 10);
obstack_grow (obs, s, strlen (s));
}
/* The rest of this file is code for builtins and expansion of user
defined macros. All the functions for builtins have a prototype as:
void m4_MACRONAME (struct obstack *obs, int argc, macro_arguments *argv);
The functions are expected to leave their expansion on the obstack OBS,
as an unfinished object. ARGV is an object representing ARGC pointers
to the individual arguments to the macro; the object may be compressed
due to references to $@ expansions, so accessors should be used. Please
note that in general argv[argc] != NULL. */
/* The first section are macros for defining, undefining, examining,
changing, ... other macros. */
/*-------------------------------------------------------------------------.
| The function define_macro is common for the builtins "define", |
| "undefine", "pushdef" and "popdef". ARGC and ARGV is as for the caller, |
| and MODE argument determines how the macro name is entered into the |
| symbol table. |
`-------------------------------------------------------------------------*/
static void
define_macro (int argc, macro_arguments *argv, symbol_lookup mode)
{
const builtin *bp;
const call_info *me = arg_info (argv);
if (bad_argc (me, argc, 1, 2))
return;
if (arg_type (argv, 1) != TOKEN_TEXT)
{
m4_warn (0, me, _("invalid macro name ignored"));
return;
}
if (argc == 2)
{
define_user_macro (ARG (1), ARG_LEN (1), "", 0, mode, 0);
return;
}
switch (arg_type (argv, 2))
{
case TOKEN_COMP:
m4_warn (0, me, _("cannot concatenate builtins"));
/* fallthru */
case TOKEN_TEXT:
define_user_macro (ARG (1), ARG_LEN (1), arg_text (argv, 2, true),
arg_len (argv, 2, true), mode, arg_quote_age (argv));
break;
case TOKEN_FUNC:
bp = find_builtin_by_addr (arg_func (argv, 2));
if (bp == NULL)
return;
else
define_builtin (ARG (1), ARG_LEN (1), bp, mode);
break;
default:
assert (!"define_macro");
abort ();
}
}
static void
m4_define (struct obstack *obs, int argc, macro_arguments *argv)
{
/* Special case if we detect that the user is appending to an
existing text macro: try to copy only the suffix into the
over-allocated slop, to avoid quadratic behavior of always
copying the prefix on each append.
Pending expansions of the existing macro must be exactly 1 (the
macro's existing definition is in use as the prefix of the
argument); if pending expansions is zero, we are not appending to
the current macro, and if it is larger than 1, then either the
macro's existing definition is in use elsewhere and must not be
invalidated, or we are at least doubling the size of the macro's
defintion which means the suffix wouldn't fit in the
pre-allocated slop anyway. */
if (argc > 2 && arg_type (argv, 1) == TOKEN_TEXT
&& arg_type (argv, 2) == TOKEN_TEXT)
{
const char *name = ARG (1);
size_t name_len = ARG_LEN (1);
symbol *s = lookup_symbol (name, name_len, SYMBOL_LOOKUP);
const char *tail;
if (s && SYMBOL_TYPE (s) == TOKEN_TEXT
&& SYMBOL_PENDING_EXPANSIONS (s) == 1
&& (tail = arg_has_prefix (argv, 2, SYMBOL_TEXT (s),
SYMBOL_TEXT_LEN (s))))
{
size_t len = ARG_LEN (2);
if (SYMBOL_TEXT_ALLOCATED (s) < len)
{
size_t new_len = len * 2;
char *old_defn = SYMBOL_TEXT (s);
if (new_len < len)
new_len = len;
SYMBOL_TEXT (s) = xcharalloc (new_len);
memcpy (SYMBOL_TEXT (s), old_defn, SYMBOL_TEXT_LEN (s));
free (old_defn);
SYMBOL_TEXT_ALLOCATED (s) = new_len;
}
memcpy (SYMBOL_TEXT (s) + SYMBOL_TEXT_LEN (s), tail,
len - SYMBOL_TEXT_LEN (s));
SYMBOL_TEXT_LEN (s) = len;
SYMBOL_TEXT_QUOTE_AGE (s) = arg_quote_age (argv);
check_macro_sequence (name, name_len, SYMBOL_TEXT (s), len);
return;
}
}
define_macro (argc, argv, SYMBOL_INSERT);
}
static void
m4_undefine (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
int i;
if (bad_argc (me, argc, 1, -1))
return;
for (i = 1; i < argc; i++)
if (arg_type (argv, i) != TOKEN_TEXT)
m4_warn (0, me, _("invalid macro name ignored"));
else
lookup_symbol (ARG (i), ARG_LEN (i), SYMBOL_DELETE);
}
static void
m4_pushdef (struct obstack *obs, int argc, macro_arguments *argv)
{
define_macro (argc, argv, SYMBOL_PUSHDEF);
}
static void
m4_popdef (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
int i;
if (bad_argc (me, argc, 1, -1))
return;
for (i = 1; i < argc; i++)
if (arg_type (argv, i) != TOKEN_TEXT)
m4_warn (0, me, _("invalid macro name ignored"));
else
lookup_symbol (ARG (i), ARG_LEN (i), SYMBOL_POPDEF);
}
/*---------------------.
| Conditionals of m4. |
`---------------------*/
static void
m4_ifdef (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
symbol *s;
if (bad_argc (me, argc, 2, 3))
return;
if (arg_type (argv, 1) != TOKEN_TEXT)
{
m4_warn (0, me, _("invalid macro name ignored"));
push_arg (obs, argv, 3);
return;
}
s = lookup_symbol (ARG (1), ARG_LEN (1), SYMBOL_LOOKUP);
push_arg (obs, argv, (s && SYMBOL_TYPE (s) != TOKEN_VOID) ? 2 : 3);
}
static void
m4_ifelse (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
int i;
if (argc == 2 || bad_argc (me, argc, 3, -1))
return;
else if (argc % 3 == 0)
/* Diagnose excess arguments if 5, 8, 11, etc., actual arguments. */
bad_argc (me, argc, 0, argc - 2);
i = 1;
argc--;
while (true)
{
if (arg_equal (argv, i, i + 1))
{
push_arg (obs, argv, i + 2);
return;
}
switch (argc)
{
case 3:
return;
case 4:
case 5:
push_arg (obs, argv, i + 3);
return;
default:
argc -= 3;
i += 3;
}
}
}
/*---------------------------------------------------------------------.
| The function dump_symbol () is for use by "dumpdef". It builds up a |
| table of all defined, un-shadowed, symbols. |
`---------------------------------------------------------------------*/
/* The structure dump_symbol_data is used to pass the information needed
from call to call to dump_symbol. */
struct dump_symbol_data
{
struct obstack *obs; /* obstack for table */
symbol **base; /* base of table */
int size; /* size of table */
};
static void
dump_symbol (symbol *sym, void *arg)
{
struct dump_symbol_data *data = (struct dump_symbol_data *) arg;
if (!SYMBOL_SHADOWED (sym) && SYMBOL_TYPE (sym) != TOKEN_VOID)
{
obstack_blank (data->obs, sizeof (symbol *));
data->base = (symbol **) obstack_base (data->obs);
data->base[data->size++] = sym;
}
}
/*------------------------------------------------------------------------.
| qsort comparison routine, for sorting the table made in m4_dumpdef (). |
`------------------------------------------------------------------------*/
static int
dumpdef_cmp (const void *s1, const void *s2)
{
const symbol *sym1 = *(const symbol **) s1;
const symbol *sym2 = *(const symbol **) s2;
size_t len1 = SYMBOL_NAME_LEN (sym1);
size_t len2 = SYMBOL_NAME_LEN (sym2);
int result = memcmp (SYMBOL_NAME (sym1), SYMBOL_NAME (sym2),
len1 < len2 ? len1 : len2);
if (!result)
result = len1 < len2 ? -1 : len2 < len1;
return result;
}
/*-------------------------------------------------------------------------.
| Implementation of "dumpdef" itself. It builds up a table of pointers to |
| symbols, sorts it and prints the sorted table. |
`-------------------------------------------------------------------------*/
static void
m4_dumpdef (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
symbol *s;
int i;
struct dump_symbol_data data;
const builtin *bp;
/* If there's no debug stream to dump to, skip all of this work. */
if (!debug)
return;
data.obs = obs;
data.base = (symbol **) obstack_base (obs);
data.size = 0;
if (argc == 1)
{
hack_all_symbols (dump_symbol, &data);
}
else
{
for (i = 1; i < argc; i++)
{
if (arg_type (argv, i) != TOKEN_TEXT)
{
m4_warn (0, me, _("invalid macro name ignored"));
continue;
}
s = lookup_symbol (ARG (i), ARG_LEN (i), SYMBOL_LOOKUP);
if (s != NULL && SYMBOL_TYPE (s) != TOKEN_VOID)
dump_symbol (s, &data);
else
m4_warn (0, me, _("undefined macro %s"),
quotearg_style_mem (locale_quoting_style, ARG (i),
ARG_LEN (i)));
}
}
/* Make table of symbols invisible to expand_macro (). */
obstack_finish (obs);
qsort (data.base, data.size, sizeof (symbol *), dumpdef_cmp);
for (; data.size > 0; --data.size, data.base++)
{
/* TODO - add debugmode(b) option to control quoting style? */
fwrite (SYMBOL_NAME (data.base[0]), 1, SYMBOL_NAME_LEN (data.base[0]),
debug);
fputc (':', debug);
fputc ('\t', debug);
switch (SYMBOL_TYPE (data.base[0]))
{
case TOKEN_TEXT:
if (debug_level & DEBUG_TRACE_QUOTE)
fwrite (curr_quote.str1, 1, curr_quote.len1, debug);
fwrite (SYMBOL_TEXT (data.base[0]), 1,
SYMBOL_TEXT_LEN (data.base[0]), debug);
if (debug_level & DEBUG_TRACE_QUOTE)
fwrite (curr_quote.str2, 1, curr_quote.len2, debug);
break;
case TOKEN_FUNC:
bp = find_builtin_by_addr (SYMBOL_FUNC (data.base[0]));
if (bp == NULL)
{
assert (!"m4_dumpdef");
abort ();
}
xfprintf (debug, "<%s>", bp->name);
break;
default:
assert (!"m4_dumpdef");
abort ();
break;
}
fputc ('\n', debug);
}
}
/*---------------------------------------------------------------------.
| The builtin "builtin" allows calls to builtin macros, even if their |
| definition has been overridden or shadowed. It is thus possible to |
| redefine builtins, and still access their original definition. This |
| macro is not available in compatibility mode. |
`---------------------------------------------------------------------*/
static void
m4_builtin (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
const builtin *bp;
const char *name;
if (bad_argc (me, argc, 1, -1))
return;
if (arg_type (argv, 1) != TOKEN_TEXT)
{
m4_warn (0, me, _("invalid macro name ignored"));
return;
}
name = ARG (1);
bp = find_builtin_by_name (name);
if (bp->func == m4_placeholder)
m4_warn (0, me, _("undefined builtin %s"),
quotearg_style_mem (locale_quoting_style, name, ARG_LEN (1)));
else
{
macro_arguments *new_argv = make_argv_ref (argv, name, ARG_LEN (1),
!bp->groks_macro_args, false);
bp->func (obs, argc - 1, new_argv);
}
}
/* Common code for indir and qindir. Surround the output in the
current quotes if QUOTE is true, being careful of changequote. */
static void
indir (struct obstack *obs, int argc, macro_arguments *argv, bool quote)
{
const call_info *me = arg_info (argv);
symbol *s;
const char *name;
size_t len;
if (bad_argc (me, argc, 1, -1))
return;
if (arg_type (argv, 1) != TOKEN_TEXT)
{
m4_warn (0, me, _("invalid macro name ignored"));
return;
}
name = ARG (1);
len = ARG_LEN (1);
s = lookup_symbol (name, len, SYMBOL_LOOKUP);
if (s == NULL || SYMBOL_TYPE (s) == TOKEN_VOID)
m4_warn (0, me, _("undefined macro %s"),
quotearg_style_mem (locale_quoting_style, name, len));
else
{
macro_arguments *new_argv = make_argv_ref (argv, name, len,
!SYMBOL_MACRO_ARGS (s),
SYMBOL_TRACED (s));
trace_prepre (arg_info (new_argv));
call_macro (s, new_argv, obs);
if (quote)
push_quote_wrapper ();
}
}
/*------------------------------------------------------------------------.
| The builtin "indir" allows indirect calls to macros, even if their name |
| is not a proper macro name. It is thus possible to define macros with |
| ill-formed names for internal use in larger macro packages. This macro |
| is not available in compatibility mode. |
`------------------------------------------------------------------------*/
static void
m4_indir (struct obstack *obs, int argc, macro_arguments *argv)
{
indir (obs, argc, argv, false);
}
/*----------------------------------------------------------------.
| The builtin "qindir" is like "indir", except that the output is |
| surrounded by the current quotes. This allows wrapping other |
| macros to provide quoted output where their output is normally |
| rescanned. |
`----------------------------------------------------------------*/
static void
m4_qindir (struct obstack *obs, int argc, macro_arguments *argv)
{
indir (obs, argc, argv, true);
}
/*-------------------------------------------------------------------------.
| The macro "defn" returns the quoted definition of the macro named by the |
| first argument. If the macro is builtin, it will push a special |
| macro-definition token on the input stack. |
`-------------------------------------------------------------------------*/
static void
m4_defn (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
symbol *s;
builtin_func *b;
int i;
if (bad_argc (me, argc, 1, -1))
return;
for (i = 1; i < argc; i++)
{
if (arg_type (argv, i) != TOKEN_TEXT)
{
m4_warn (0, me, _("invalid macro name ignored"));
continue;
}
s = lookup_symbol (ARG (i), ARG_LEN (i), SYMBOL_LOOKUP);
if (s == NULL)
{
m4_warn (0, me, _("undefined macro %s"),
quotearg_style_mem (locale_quoting_style, ARG (i),
ARG_LEN (i)));
continue;
}
switch (SYMBOL_TYPE (s))
{
case TOKEN_TEXT:
obstack_grow (obs, curr_quote.str1, curr_quote.len1);
push_defn (s);
obstack_grow (obs, curr_quote.str2, curr_quote.len2);
break;
case TOKEN_FUNC:
b = SYMBOL_FUNC (s);
if (b == m4_placeholder)
m4_warn (0, me,
_("builtin %s requested by frozen file not found"),
quotearg_style_mem (locale_quoting_style, ARG (i),
ARG_LEN (i)));
else
push_macro (obs, b);
break;
default:
assert (!"m4_defn");
abort ();
}
}
}
/*------------------------------------------------------------------------.
| This section contains macros to handle the builtins "syscmd", "esyscmd" |
| and "sysval". "esyscmd" is GNU specific. |
`------------------------------------------------------------------------*/
/* Helper macros for readability. */
#if UNIX || defined WEXITSTATUS
# define M4SYSVAL_EXITBITS(status) \
(WIFEXITED (status) ? WEXITSTATUS (status) : 0)
# define M4SYSVAL_TERMSIGBITS(status) \
(WIFSIGNALED (status) ? WTERMSIG (status) << 8 : 0)
#else /* !UNIX && !defined WEXITSTATUS */
/* Platforms such as mingw do not support the notion of reporting
which signal terminated a process. Furthermore if WEXITSTATUS was
not provided, then the exit value is in the low eight bits. */
# define M4SYSVAL_EXITBITS(status) status
# define M4SYSVAL_TERMSIGBITS(status) 0
#endif /* !UNIX && !defined WEXITSTATUS */
/* Fallback definitions if <stdlib.h> or <sys/wait.h> are inadequate. */
#ifndef WEXITSTATUS
# define WEXITSTATUS(status) (((status) >> 8) & 0xff)
#endif
#ifndef WTERMSIG
# define WTERMSIG(status) ((status) & 0x7f)
#endif
#ifndef WIFSIGNALED
# define WIFSIGNALED(status) (WTERMSIG (status) != 0)
#endif
#ifndef WIFEXITED
# define WIFEXITED(status) (WTERMSIG (status) == 0)
#endif
/* Exit code from last "syscmd" command. */
static int sysval;
static void
m4_syscmd (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
const char *cmd = ARG (1);
size_t len = ARG_LEN (1);
if (strlen (cmd) != len)
m4_warn (0, me, _("argument %s truncated"),
quotearg_style_mem (locale_quoting_style, cmd, len));
if (bad_argc (me, argc, 1, 1) || !*cmd)
{
/* The empty command is successful. */
sysval = 0;
return;
}
debug_flush_files ();
sysval = system (cmd);
#if FUNC_SYSTEM_BROKEN
/* OS/2 has a buggy system() that returns exit status in the lowest eight
bits, although pclose() and WEXITSTATUS are defined to return exit
status in the next eight bits. This approach can't detect signals, but
at least syscmd(`ls') still works when stdout is a terminal. An
alternate approach is popen/insert_file/pclose, but that makes stdout
a pipe, which can change how some child processes behave. */
if (sysval != -1)
sysval <<= 8;
#endif /* FUNC_SYSTEM_BROKEN */
}
static void
m4_esyscmd (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
const char *cmd = ARG (1);
size_t len = ARG_LEN (1);
FILE *pin;
int ch;
if (strlen (cmd) != len)
m4_warn (0, me, _("argument %s truncated"),
quotearg_style_mem (locale_quoting_style, cmd, len));
if (bad_argc (me, argc, 1, 1) || !*cmd)
{
/* The empty command is successful. */
sysval = 0;
return;
}
debug_flush_files ();
errno = 0;
pin = popen (cmd, "r");
if (pin == NULL)
{
m4_warn (errno, me, _("cannot open pipe to command %s"),
quotearg_style (locale_quoting_style, cmd));
sysval = -1;
}
else
{
while ((ch = getc (pin)) != EOF)
obstack_1grow (obs, (char) ch);
sysval = pclose (pin);
}
}
static void
m4_sysval (struct obstack *obs, int argc, macro_arguments *argv)
{
shipout_int (obs, (sysval == -1 ? 127
: (M4SYSVAL_EXITBITS (sysval)
| M4SYSVAL_TERMSIGBITS (sysval))));
}
/*-------------------------------------------------------------------------.
| This section contains the top level code for the "eval" builtin. The |
| actual work is done in the function evaluate (), which lives in eval.c. |
`-------------------------------------------------------------------------*/
static void
m4_eval (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
int32_t value = 0;
int radix = 10;
int min = 1;
const char *s;
size_t len;
if (bad_argc (me, argc, 1, 3))
return;
if (!arg_empty (argv, 2) && !numeric_arg (me, ARG (2), ARG_LEN (2), &radix))
return;
if (radix < 1 || radix > 36)
{
m4_warn (0, me, _("radix %d out of range"), radix);
return;
}
if (argc >= 4 && !numeric_arg (me, ARG (3), ARG_LEN (3), &min))
return;
if (min < 0)
{
m4_warn (0, me, _("negative width"));
return;
}
if (evaluate (me, ARG (1), ARG_LEN (1), &value))
return;
if (radix == 1)
{
if (value < 0)
{
obstack_1grow (obs, '-');
value = -value;
}
if ((uint32_t) value < min)
{
obstack_blank (obs, min - value);
memset ((char *) obstack_next_free (obs) - (min - value), '0',
min - value);
}
obstack_blank (obs, value);
memset ((char *) obstack_next_free (obs) - value, '1', value);
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
m4_incr (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
int value;
if (bad_argc (me, argc, 1, 1))
return;
if (!numeric_arg (me, ARG (1), ARG_LEN (1), &value))
return;
shipout_int (obs, value + 1);
}
static void
m4_decr (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
int value;
if (bad_argc (me, argc, 1, 1))
return;
if (!numeric_arg (me, ARG (1), ARG_LEN (1), &value))
return;
shipout_int (obs, value - 1);
}
/* This section contains the macros "divert", "undivert" and "divnum" for
handling diversion. The utility functions used lives in output.c. */
/*-----------------------------------------------------------------------.
| Divert further output to the diversion given by ARGV[1]. Out of range |
| means discard further output. |
`-----------------------------------------------------------------------*/
static void
m4_divert (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
int i = 0;
bad_argc (me, argc, 0, 1);
if (argc >= 2 && !numeric_arg (me, ARG (1), ARG_LEN (1), &i))
return;
make_diversion (i);
}
/*-----------------------------------------------------.
| Expand to the current diversion number, -1 if none. |
`-----------------------------------------------------*/
static void
m4_divnum (struct obstack *obs, int argc, macro_arguments *argv)
{
bad_argc (arg_info (argv), argc, 0, 0);
shipout_int (obs, current_diversion);
}
/*-----------------------------------------------------------------------.
| Bring back the diversion given by the argument list. If none is |
| specified, bring back all diversions. GNU specific is the option of |
| undiverting named files, by passing a non-numeric argument to undivert |
| (). |
`-----------------------------------------------------------------------*/
static void
m4_undivert (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
int i;
int file;
FILE *fp;
char *endp;
if (argc == 1)
undivert_all ();
else
for (i = 1; i < argc; i++)
{
const char *str = ARG (i);
size_t len = ARG_LEN (i);
file = strtol (str, &endp, 10);
if (endp - str == len && !isspace (to_uchar (*str)))
insert_diversion (file);
else if (no_gnu_extensions)
m4_warn (0, me, _("non-numeric argument %s"),
quotearg_style_mem (locale_quoting_style, str, len));
else if (strlen (str) != len)
m4_warn (0, me, _("invalid file name %s"),
quotearg_style_mem (locale_quoting_style, str, len));
else
{
fp = m4_path_search (str, NULL);
if (fp != NULL)
{
insert_file (fp);
if (fclose (fp) == EOF)
m4_warn (errno, me, _("error undiverting %s"),
quotearg_style (locale_quoting_style, str));
}
else
m4_warn (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 "changeword". */
/*------------------------------------------------------------------------.
| Delete all subsequent whitespace from input. The function skip_line () |
| lives in input.c. |
`------------------------------------------------------------------------*/
static void
m4_dnl (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
bad_argc (me, argc, 0, 0);
skip_line (me);
}
/*--------------------------------------------------------------------.
| Shift all arguments one to the left, discarding the first |
| argument. Each output argument is quoted with the current quotes. |
`--------------------------------------------------------------------*/
static void
m4_shift (struct obstack *obs, int argc, macro_arguments *argv)
{
if (bad_argc (arg_info (argv), argc, 1, -1))
return;
push_args (obs, argv, true, true);
}
/*--------------------------------------------------------------------------.
| Change the current quotes. The function set_quotes () lives in input.c. |
`--------------------------------------------------------------------------*/
static void
m4_changequote (struct obstack *obs, int argc, macro_arguments *argv)
{
bad_argc (arg_info (argv), argc, 0, 2);
/* Explicit NULL distinguishes between empty and missing argument. */
set_quotes ((argc >= 2) ? ARG (1) : NULL, ARG_LEN (1),
(argc >= 3) ? ARG (2) : NULL, ARG_LEN (2));
}
/*--------------------------------------------------------------------.
| Change the current comment delimiters. The function set_comment () |
| lives in input.c. |
`--------------------------------------------------------------------*/
static void
m4_changecom (struct obstack *obs, int argc, macro_arguments *argv)
{
bad_argc (arg_info (argv), argc, 0, 2);
/* Explicit NULL distinguishes between empty and missing argument. */
set_comment ((argc >= 2) ? ARG (1) : NULL, ARG_LEN (1),
(argc >= 3) ? ARG (2) : NULL, ARG_LEN (2));
}
#ifdef ENABLE_CHANGEWORD
/*-----------------------------------------------------------------------.
| Change the regular expression used for breaking the input into words. |
| The function set_word_regexp () lives in input.c. |
`-----------------------------------------------------------------------*/
static void
m4_changeword (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
if (bad_argc (me, argc, 1, 1))
return;
set_word_regexp (me, ARG (1), ARG_LEN (1));
}
#endif /* ENABLE_CHANGEWORD */
/* This section contains macros for inclusion of other files -- "include"
and "sinclude". This differs from bringing back diversions, in that
the input is scanned before being copied to the output. */
/*-------------------------------------------------------------------------.
| Generic include function. Include the file given by the first argument, |
| if it exists. Complain about inaccesible files iff SILENT is false. |
`-------------------------------------------------------------------------*/
static void
include (int argc, macro_arguments *argv, bool silent)
{
const call_info *me = arg_info (argv);
FILE *fp;
char *name;
const char *arg;
size_t len;
if (bad_argc (me, argc, 1, 1))
return;
arg = ARG (1);
len = ARG_LEN (1);
if (strlen (arg) != len)
m4_warn (0, me, _("argument %s truncated"),
quotearg_style_mem (locale_quoting_style, arg, len));
fp = m4_path_search (arg, &name);
if (fp == NULL)
{
if (!silent)
m4_error (0, errno, me, _("cannot open %s"),
quotearg_style (locale_quoting_style, arg));
return;
}
push_file (fp, name, true);
free (name);
}
/*------------------------------------------------.
| Include a file, complaining in case of errors. |
`------------------------------------------------*/
static void
m4_include (struct obstack *obs, int argc, macro_arguments *argv)
{
include (argc, argv, false);
}
/*----------------------------------.
| Include a file, ignoring errors. |
`----------------------------------*/
static void
m4_sinclude (struct obstack *obs, int argc, macro_arguments *argv)
{
include (argc, argv, true);
}
/* More miscellaneous builtins -- "maketemp", "errprint", "__file__",
"__line__", and "__program__". The last three are GNU specific. */
/*-----------------------------------------------------------------.
| Use the first argument as a template for a temporary file name. |
`-----------------------------------------------------------------*/
/* Add trailing 'X' to PATTERN of length LEN as necessary, then
securely create the file, and place the quoted new file name on
OBS. Report errors on behalf of ME. */
static void
mkstemp_helper (struct obstack *obs, const call_info *me, const char *pattern,
size_t len)
{
int fd;
int i;
char *name;
/* Guarantee that there are six trailing 'X' characters, even if the
user forgot to supply them. Output must be quoted if
successful. */
obstack_grow (obs, curr_quote.str1, curr_quote.len1);
if (strlen (pattern) < len)
{
m4_warn (0, me, _("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) + curr_quote.len1;
errno = 0;
fd = mkstemp (name);
if (fd < 0)
{
m4_warn (errno, me, _("cannot create file from template %s"),
quotearg_style (locale_quoting_style, pattern));
obstack_free (obs, obstack_finish (obs));
}
else
{
close (fd);
/* Remove NUL, then finish quote. */
obstack_blank (obs, -1);
obstack_grow (obs, curr_quote.str2, curr_quote.len2);
}
}
static void
m4_maketemp (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
if (bad_argc (me, argc, 1, 1))
return;
m4_warn (0, me, _("recommend using mkstemp instead"));
if (no_gnu_extensions)
{
/* POSIX states "any trailing 'X' characters [are] replaced with
the current process ID as a string", without referencing the
file system. Horribly insecure, but we have to do it when we
are in traditional mode.
For reference, Solaris m4 does:
maketemp() -> `'
maketemp(X) -> `X'
maketemp(XX) -> `Xn', where n is last digit of pid
maketemp(XXXXXXXX) -> `X00nnnnn', where nnnnn is 16-bit pid
*/
const char *str = ARG (1);
size_t len = ARG_LEN (1);
size_t i;
struct obstack *scratch = arg_scratch ();
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", len - i - pid_len, 0, pid);
}
else
mkstemp_helper (obs, me, ARG (1), ARG_LEN (1));
}
static void
m4_mkstemp (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
if (bad_argc (me, argc, 1, 1))
return;
mkstemp_helper (obs, me, ARG (1), ARG_LEN (1));
}
/*----------------------------------------.
| Print all arguments on standard error. |
`----------------------------------------*/
static void
m4_errprint (struct obstack *obs, int argc, macro_arguments *argv)
{
size_t len;
if (bad_argc (arg_info (argv), argc, 1, -1))
return;
arg_print (obs, argv, 1, NULL, true, NULL, " ", NULL, false);
debug_flush_files ();
len = obstack_object_size (obs);
/* The close_stdin module makes it safe to skip checking the return
value here. */
fwrite (obstack_finish (obs), 1, len, stderr);
fflush (stderr);
}
static void
m4___file__ (struct obstack *obs, int argc, macro_arguments *argv)
{
bad_argc (arg_info (argv), argc, 0, 0);
obstack_grow (obs, curr_quote.str1, curr_quote.len1);
obstack_grow (obs, current_file, strlen (current_file));
obstack_grow (obs, curr_quote.str2, curr_quote.len2);
}
static void
m4___line__ (struct obstack *obs, int argc, macro_arguments *argv)
{
bad_argc (arg_info (argv), argc, 0, 0);
shipout_int (obs, current_line);
}
static void
m4___program__ (struct obstack *obs, int argc, macro_arguments *argv)
{
bad_argc (arg_info (argv), argc, 0, 0);
obstack_grow (obs, curr_quote.str1, curr_quote.len1);
obstack_grow (obs, program_name, strlen (program_name));
obstack_grow (obs, curr_quote.str2, curr_quote.len2);
}
/* 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. |
`-------------------------------------------------------------------------*/
static void
m4_m4exit (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
int exit_code = EXIT_SUCCESS;
/* Warn on bad arguments, but still exit. */
bad_argc (me, argc, 0, 1);
if (argc >= 2 && !numeric_arg (me, ARG (1), ARG_LEN (1), &exit_code))
exit_code = EXIT_FAILURE;
if (exit_code < 0 || exit_code > 255)
{
m4_warn (0, me, _("exit status out of range: %d"), exit_code);
exit_code = EXIT_FAILURE;
}
/* Change debug stream back to stderr, to force flushing debug stream and
detect any errors it might have encountered. */
debug_set_output (me, NULL);
debug_flush_files ();
if (exit_code == EXIT_SUCCESS && retcode != EXIT_SUCCESS)
exit_code = retcode;
/* Propagate non-zero status to atexit handlers. */
if (exit_code != EXIT_SUCCESS)
exit_failure = exit_code;
exit (exit_code);
}
/*-----------------------------------------------------------------.
| Save the argument text in FIFO order until EOF has been seen, |
| allowing for user specified cleanup action. Extra arguments are |
| saved when not in POSIX mode. |
`-----------------------------------------------------------------*/
static void
m4_m4wrap (struct obstack *obs, int argc, macro_arguments *argv)
{
if (bad_argc (arg_info (argv), argc, 1, -1))
return;
wrap_args (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. */
/*-----------------------------------------------------------------------.
| Set_trace () is used by "traceon" and "traceoff" to enable and disable |
| tracing of a macro. It disables tracing if DATA is NULL, otherwise it |
| enable tracing. |
`-----------------------------------------------------------------------*/
static void
set_trace (symbol *sym, void *data)
{
SYMBOL_TRACED (sym) = data != NULL;
/* Remove placeholder from table if macro is undefined and untraced. */
if (SYMBOL_TYPE (sym) == TOKEN_VOID && data == NULL)
lookup_symbol (SYMBOL_NAME (sym), SYMBOL_NAME_LEN (sym), SYMBOL_POPDEF);
}
static void
m4_traceon (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
symbol *s;
int i;
if (argc == 1)
hack_all_symbols (set_trace, obs);
else
for (i = 1; i < argc; i++)
{
if (arg_type (argv, i) != TOKEN_TEXT)
{
m4_warn (0, me, _("invalid macro name ignored"));
continue;
}
s = lookup_symbol (ARG (i), ARG_LEN (i), SYMBOL_LOOKUP);
if (!s)
s = lookup_symbol (ARG (i), ARG_LEN (i), SYMBOL_INSERT);
set_trace (s, obs);
}
}
/*------------------------------------------------------------------------.
| Disable tracing of all specified macros, or all, if none is specified. |
`------------------------------------------------------------------------*/
static void
m4_traceoff (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
symbol *s;
int i;
if (argc == 1)
hack_all_symbols (set_trace, NULL);
else
for (i = 1; i < argc; i++)
{
if (arg_type (argv, i) != TOKEN_TEXT)
{
m4_warn (0, me, _("invalid macro name ignored"));
continue;
}
s = lookup_symbol (ARG (i), ARG_LEN (i), SYMBOL_LOOKUP);
if (s != NULL)
set_trace (s, NULL);
}
}
/*----------------------------------------------------------------------.
| On-the-fly control of the format of the tracing output. It takes one |
| argument, which is a character string like given to the -d option, or |
| none in which case the debug_level is zeroed. |
`----------------------------------------------------------------------*/
static void
m4_debugmode (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
const char *str = ARG (1);
size_t len = ARG_LEN (1);
int new_debug_level;
int change_flag;
bad_argc (me, argc, 0, 1);
if (argc == 1)
debug_level = 0;
else
{
if (*str == '+' || *str == '-')
{
change_flag = *str;
new_debug_level = debug_decode (str + 1, len - 1);
}
else
{
change_flag = 0;
new_debug_level = debug_decode (str, len);
}
if (new_debug_level < 0)
m4_warn (0, me, _("bad debug flags: %s"),
quotearg_style_mem (locale_quoting_style, str, len));
else
{
switch (change_flag)
{
case 0:
debug_level = new_debug_level;
break;
case '+':
debug_level |= new_debug_level;
break;
case '-':
debug_level &= ~new_debug_level;
break;
}
}
}
}
/*-------------------------------------------------------------------------.
| Specify the destination of the debugging output. With one argument, the |
| argument is taken as a file name, with no arguments, revert to stderr. |
`-------------------------------------------------------------------------*/
static void
m4_debugfile (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
bad_argc (me, argc, 0, 1);
if (argc == 1)
debug_set_output (me, NULL);
else
{
const char *str = ARG (1);
size_t len = ARG_LEN (1);
if (strlen (str) < len)
m4_warn (0, me, _("argument %s truncated"),
quotearg_style_mem (locale_quoting_style, str, len));
if (!debug_set_output (me, str))
m4_warn (errno, me, _("cannot set debug file %s"),
quotearg_style (locale_quoting_style, str));
}
}
/* This section contains text processing macros: "len", "index",
"substr", "translit", "format", "regexp" and "patsubst". The last
three are GNU specific. */
/*---------------------------------------------.
| Expand to the length of the first argument. |
`---------------------------------------------*/
static void
m4_len (struct obstack *obs, int argc, macro_arguments *argv)
{
if (bad_argc (arg_info (argv), argc, 1, 1))
return;
shipout_int (obs, ARG_LEN (1));
}
/*-------------------------------------------------------------------------.
| The macro expands to the first index of the second argument in the first |
| argument. |
`-------------------------------------------------------------------------*/
static void
m4_index (struct obstack *obs, int argc, macro_arguments *argv)
{
const char *haystack;
const char *needle;
const char *result = NULL;
int retval = -1;
if (bad_argc (arg_info (argv), argc, 2, 2))
{
/* builtin(`index') is blank, but index(`abc') is 0. */
if (argc == 2)
shipout_int (obs, 0);
return;
}
haystack = ARG (1);
needle = ARG (2);
/* Rely on the optimizations guaranteed by gnulib's memmem
module. */
result = (char *) memmem (haystack, ARG_LEN (1), needle, ARG_LEN (2));
if (result)
retval = result - haystack;
shipout_int (obs, retval);
}
/*-------------------------------------------------------------------------.
| The macro "substr" extracts substrings from the first argument, starting |
| from the index given by the second argument, extending for a length |
| given by the third argument. If the third argument is missing, the |
| substring extends to the end of the first argument. |
`-------------------------------------------------------------------------*/
static void
m4_substr (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
int start = 0;
int length;
int avail;
if (bad_argc (me, argc, 2, 3))
{
/* builtin(`substr') is blank, but substr(`abc') is abc. */
if (argc == 2)
push_arg (obs, argv, 1);
return;
}
length = avail = ARG_LEN (1);
if (!numeric_arg (me, ARG (2), ARG_LEN (2), &start))
return;
if (argc >= 4 && !numeric_arg (me, ARG (3), ARG_LEN (3), &length))
return;
if (start < 0 || length <= 0 || start >= avail)
return;
if (start + length > avail)
length = avail - start;
obstack_grow (obs, ARG (1) + start, length);
}
/*------------------------------------------------------------------.
| For "translit", ranges are allowed in the second and third |
| argument. They are expanded in the following function, and the |
| expanded strings, without any ranges left, are used to translate |
| the characters of the first argument. A single - (dash) can be |
| included in the strings by being the first or the last character |
| in the string. If the first character in a range is after the |
| first in the character set, the range is made backwards, thus 9-0 |
| is the string 9876543210. This function expands S of length *LEN |
| using OBS for the expansion, sets *LEN to the new length, and |
| returns the expansion. |
`------------------------------------------------------------------*/
static const char *
expand_ranges (const char *s, size_t *len, struct obstack *obs)
{
unsigned char from;
unsigned char to;
const char *end = s + *len;
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 (pueh). |
`----------------------------------------------------------------------*/
static void
m4_translit (struct obstack *obs, int argc, macro_arguments *argv)
{
const char *data;
const char *from;
const char *to;
size_t from_len;
size_t to_len;
char map[256] = {0};
char found[256] = {0};
unsigned char ch;
enum { ASIS, REPLACE, DELETE };
if (bad_argc (arg_info (argv), argc, 2, 3) || arg_empty (argv, 1)
|| arg_empty (argv, 2))
{
/* builtin(`translit') is blank, but translit(`abc') is abc. */
if (argc >= 2)
push_arg (obs, argv, 1);
return;
}
from = ARG (2);
from_len = ARG_LEN (2);
if (memchr (from, '-', from_len) != NULL)
from = expand_ranges (from, &from_len, arg_scratch ());
to = ARG (3);
to_len = ARG_LEN (3);
if (memchr (to, '-', to_len) != NULL)
to = expand_ranges (to, &to_len, arg_scratch ());
assert (from && to);
/* Calling strchr(from) for each character in data is quadratic,
since both strings can be arbitrarily long. Instead, create a
from-to mapping in one pass of from, then use that map in one
pass of data, for linear behavior. Traditional behavior is that
only the first instance of a character in from is consulted,
hence the found map. */
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 = ARG (1);
from_len = ARG_LEN (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 (!"m4_translit");
abort ();
}
}
}
/*--------------------------------------------------------------.
| Frontend for *printf like formatting. The function format () |
| lives in the file format.c. |
`--------------------------------------------------------------*/
static void
m4_format (struct obstack *obs, int argc, macro_arguments *argv)
{
if (bad_argc (arg_info (argv), argc, 1, -1))
return;
expand_format (obs, argc, argv);
}
/*-------------------------------------------------------------------------.
| Function to perform substitution by regular expressions. Used by the |
| builtins regexp and patsubst. The changed text is placed on the |
| obstack. The substitution is REPL, with \& substituted by this part of |
| VICTIM matched by the last whole regular expression, taken from REGS[0], |
| and \N substituted by the text matched by the Nth parenthesized |
| sub-expression, taken from REGS[N]. |
`-------------------------------------------------------------------------*/
static int substitute_warned = 0;
static void
substitute (struct obstack *obs, const call_info *me, const char *victim,
const char *repl, size_t repl_len, struct re_registers *regs)
{
int ch;
while (repl_len--)
{
ch = *repl++;
if (ch != '\\')
{
obstack_1grow (obs, ch);
continue;
}
if (!repl_len)
{
m4_warn (0, me, _("trailing \\ ignored in replacement"));
return;
}
ch = *repl++;
repl_len--;
switch (ch)
{
case '0':
if (!substitute_warned)
{
m4_warn (0, me, _("\
\\0 will disappear, use \\& instead in replacements"));
substitute_warned = 1;
}
/* Fall through. */
case '&':
if (regs)
obstack_grow (obs, victim + regs->start[0],
regs->end[0] - 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 (!regs || regs->num_regs - 1 <= ch)
m4_warn (0, me, _("sub-expression %d not present"), ch);
else if (regs->end[ch] > 0)
obstack_grow (obs, victim + regs->start[ch],
regs->end[ch] - regs->start[ch]);
break;
default:
obstack_1grow (obs, ch);
break;
}
}
}
/*------------------------------------------.
| Initialize regular expression variables. |
`------------------------------------------*/
void
init_pattern_buffer (struct re_pattern_buffer *buf, struct re_registers *regs)
{
buf->translate = NULL;
buf->fastmap = NULL;
buf->buffer = NULL;
buf->allocated = 0;
if (regs)
{
regs->start = NULL;
regs->end = NULL;
}
}
/*------------------------------------------------------------------.
| Regular expression version of index. Given two arguments, expand |
| to the index of the first match of the second argument (a regexp) |
| in the first. Expand to -1 if there is no match. Given a third |
| argument, a match is substituted according to this argument. |
`------------------------------------------------------------------*/
static void
m4_regexp (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
const char *victim; /* first argument */
const char *regexp; /* regular expression */
const char *repl; /* replacement string */
struct re_pattern_buffer *buf;/* compiled regular expression */
struct re_registers *regs; /* for subexpression matches */
const char *msg; /* error message from re_compile_pattern */
int startpos; /* start position of match */
int length; /* length of first argument */
if (bad_argc (me, argc, 2, 3))
{
/* builtin(`regexp') is blank, but regexp(`abc') is 0. */
if (argc == 2)
shipout_int (obs, 0);
return;
}
victim = ARG (1);
regexp = ARG (2);
repl = ARG (3);
if (arg_empty (argv, 2))
{
/* The empty regex matches everything! */
if (argc == 3)
shipout_int (obs, 0);
else
substitute (obs, me, victim, repl, ARG_LEN (3), NULL);
return;
}
#ifdef DEBUG_REGEX
if (trace_file)
{
fputs ("r:{", trace_file);
fwrite (regexp, 1, ARG_LEN (2), trace_file);
if (argc > 3)
{
fputs ("}:{", trace_file);
fwrite (repl, 1, ARG_LEN (3), trace_file);
}
fputs ("}\n", trace_file);
}
#endif /* DEBUG_REGEX */
msg = compile_pattern (regexp, ARG_LEN (2), &buf, &regs);
if (msg != NULL)
{
m4_warn (0, me, _("bad regular expression %s: %s"),
quotearg_style_mem (locale_quoting_style, regexp, ARG_LEN (2)),
msg);
return;
}
length = ARG_LEN (1);
/* Avoid overhead of allocating regs if we won't use it. */
startpos = re_search (buf, victim, length, 0, length,
argc == 3 ? NULL : regs);
if (startpos == -2)
m4_warn (0, me, _("problem matching regular expression %s"),
quotearg_style_mem (locale_quoting_style, regexp, ARG_LEN (2)));
else if (argc == 3)
shipout_int (obs, startpos);
else if (startpos >= 0)
substitute (obs, me, victim, repl, ARG_LEN (3), regs);
}
/*------------------------------------------------------------------.
| 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 third argument, with \& substituted by the matched |
| text, and \N substituted by the text matched by the Nth |
| parenthesized sub-expression. |
`------------------------------------------------------------------*/
static void
m4_patsubst (struct obstack *obs, int argc, macro_arguments *argv)
{
const call_info *me = arg_info (argv);
const char *victim; /* First argument. */
const char *regexp; /* Regular expression. */
const char *repl; /* Replacement text. */
struct re_pattern_buffer *buf;/* Compiled regular expression. */
struct re_registers *regs; /* For subexpression matches. */
const char *msg; /* Error message from re_compile_pattern. */
int matchpos; /* Start position of match. */
int offset; /* Current match offset. */
int length; /* Length of first argument. */
size_t repl_len; /* Length of replacement. */
if (bad_argc (me, argc, 2, 3))
{
/* builtin(`patsubst') is blank, but patsubst(`abc') is abc. */
if (argc == 2)
push_arg (obs, argv, 1);
return;
}
/* The empty regex matches everywhere, but if there is no
replacement, we need not waste time with it. */
if (arg_empty (argv, 2) && arg_empty (argv, 3))
{
push_arg (obs, argv, 1);
return;
}
victim = ARG (1);
regexp = ARG (2);
repl = ARG (3);
repl_len = ARG_LEN (3);
#ifdef DEBUG_REGEX
if (trace_file)
{
fputs ("p:{", trace_file);
fwrite (regexp, 1, ARG_LEN (2), trace_file);
fputs ("}:{", trace_file);
fwrite (repl, 1, repl_len, trace_file);
fputs ("}\n", trace_file);
}
#endif /* DEBUG_REGEX */
msg = compile_pattern (regexp, ARG_LEN (2), &buf, &regs);
if (msg != NULL)
{
m4_warn (0, me, _("bad regular expression %s: %s"),
quotearg_style_mem (locale_quoting_style, regexp, ARG_LEN (2)),
msg);
return;
}
length = ARG_LEN (1);
offset = 0;
matchpos = 0;
while (offset <= length)
{
matchpos = re_search (buf, victim, length,
offset, length - offset, regs);
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_warn (0, me, _("problem matching regular expression %s"),
quotearg_style_mem (locale_quoting_style, regexp,
ARG_LEN (2)));
else if (offset < length)
obstack_grow (obs, victim + offset, length - offset);
break;
}
/* Copy the part of the string that was skipped by re_search (). */
if (matchpos > offset)
obstack_grow (obs, victim + offset, matchpos - offset);
/* Handle the part of the string that was covered by the match. */
substitute (obs, me, victim, repl, repl_len, regs);
/* Update the offset to the end of the match. If the regexp
matched a null string, advance offset one more, to avoid
infinite loops. */
offset = regs->end[0];
if (regs->start[0] == regs->end[0])
{
if (offset < length)
obstack_1grow (obs, victim[offset]);
offset++;
}
}
}
/* Finally, a placeholder builtin. This builtin is not installed by
default, but when reading back frozen files, this is associated
with any builtin we don't recognize (for example, if the frozen
file was created with a changeword capable m4, but is then loaded
by a different m4 that does not support changeword). This way, we
can keep 'm4 -R' quiet in the common case that the user did not
know or care about the builtin when the frozen file was created,
while still flagging it as a potential error if an attempt is made
to actually use the builtin. */
/*--------------------------------------------------------------------.
| Issue a warning that this macro is a placeholder for an unsupported |
| builtin that was requested while reloading a frozen file. |
`--------------------------------------------------------------------*/
void
m4_placeholder (struct obstack *obs, int argc, macro_arguments *argv)
{
m4_warn (0, NULL, _("builtin %s requested by frozen file not found"),
quotearg_style_mem (locale_quoting_style, ARG (0), ARG_LEN (0)));
}
/*-------------------------------------------------------------------------.
| This function handles all expansion of user defined and predefined |
| macros. It is called with an obstack OBS, where the macros expansion |
| will be placed, as an unfinished object. SYM points to the macro |
| definition, giving the expansion text. ARGC and ARGV are the arguments, |
| as usual. |
`-------------------------------------------------------------------------*/
void
expand_user_macro (struct obstack *obs, symbol *sym,
int argc, macro_arguments *argv)
{
const char *text = SYMBOL_TEXT (sym);
size_t len = SYMBOL_TEXT_LEN (sym);
int i;
const char *dollar = memchr (text, '$', len);
if (!dollar)
{
push_defn (sym);
return;
}
while (dollar)
{
obstack_grow (obs, text, dollar - text);
len -= dollar - text;
text = dollar;
if (len == 1)
break;
len--;
switch (*++text)
{
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
if (no_gnu_extensions)
{
i = *text++ - '0';
len--;
}
else
{
for (i = 0; len && isdigit (to_uchar (*text)); text++, len--)
i = i * 10 + (*text - '0');
}
push_arg (obs, argv, i);
break;
case '#': /* number of arguments */
shipout_int (obs, argc - 1);
text++;
len--;
break;
case '*': /* all arguments */
case '@': /* ... same, but quoted */
push_args (obs, argv, false, *text == '@');
text++;
len--;
break;
default:
obstack_1grow (obs, '$');
break;
}
dollar = memchr (text, '$', len);
}
obstack_grow (obs, text, len);
}