mirror of
https://https.git.savannah.gnu.org/git/m4.git
synced 2026-01-27 18:04:52 +00:00
* m4/module.c (m4__module_open): Instead of calling lt_dlopenext right away, use the preload hint first incase path searching has been disabled by POSIXLY_CORRECT. Otherwise fallback to a manual path search to override libltdl's algorithm. * m4/m4module.h: Adjust. * m4/path.c (FILE_SUFFIXES): Order to try suffixes in our path search algorithm. (NO_SUFFIXES): Alternatively, how to to search a path without adding file suffixes. (m4_path_search): Add a new suffixes parameter defaulting to NO_SUFFIXES. Adjust all callers. (m4_load_filename): New generic load function that tries to open a matched file first as a module, and if that fails fall back to as (possibly frozen) m4 input. (m4_fopen): New function with close on exec functionality. * modules/m4.c (include, sinclude): Use m4_load_filename to overload these builtins to work with modules too. * modules/load.c (load): Removed this builtin. * src/main.c: Remove Dynamic loading features section, and M4MODPATH support. (long_options): Remove "load-module", "unload-module" and "module-directory" options. (OPTSTRING): Remove 'M' and 'm' options. (process_file): Use m4_load_filename to support module names on the command line. * Makefile.am (TESTS_ENVIRONMENT): No need for abs_top_builddir any more. * doc/m4.texinfo: Update examples. (Dynamic loading features, Load): Removed. (Command line files): Describe how non-option arguments are treated to try to find a matching macro file or dso. (Include): Describe additional dso loading features if a suitable text file cannot be found. (Modules): Updated. * tests/builtins.at, tests/m4.in, tests/modules.at, tests/options.at: Adjust test cases and calling conventions to new semantics. * NEWS: Updated.
1171 lines
33 KiB
C
1171 lines
33 KiB
C
/* GNU m4 -- A simple macro processor
|
||
Copyright (C) 2000, 2002, 2003, 2004, 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, unless 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 "stdlib--.h"
|
||
#include "tempname.h"
|
||
#include "unistd--.h"
|
||
|
||
#if HAVE_SYS_WAIT_H
|
||
# include <sys/wait.h>
|
||
#endif
|
||
|
||
#include <modules/m4.h>
|
||
|
||
/* Rename exported symbols for dlpreload()ing. */
|
||
#define m4_builtin_table m4_LTX_m4_builtin_table
|
||
|
||
#define m4_set_sysval m4_LTX_m4_set_sysval
|
||
#define m4_sysval_flush m4_LTX_m4_sysval_flush
|
||
#define m4_dump_symbols m4_LTX_m4_dump_symbols
|
||
#define m4_expand_ranges m4_LTX_m4_expand_ranges
|
||
#define m4_make_temp m4_LTX_m4_make_temp
|
||
|
||
extern void m4_set_sysval (int value);
|
||
extern void m4_sysval_flush (m4 *context, bool report);
|
||
extern void m4_dump_symbols (m4 *context, m4_dump_symbol_data *data,
|
||
size_t argc, m4_macro_args *argv, bool complain);
|
||
extern const char *m4_expand_ranges (const char *s, m4_obstack *obs);
|
||
extern void m4_make_temp (m4 *context, m4_obstack *obs, const char *macro,
|
||
const char *name, size_t len, bool dir);
|
||
|
||
/* Maintain each of the builtins implemented in this modules along
|
||
with their details in a single table for easy maintenance.
|
||
|
||
function macros blind side minargs maxargs */
|
||
#define builtin_functions \
|
||
BUILTIN (changecom, false, false, false, 0, 2 ) \
|
||
BUILTIN (changequote, false, false, false, 0, 2 ) \
|
||
BUILTIN (decr, false, true, true, 1, 1 ) \
|
||
BUILTIN (define, true, true, false, 1, 2 ) \
|
||
BUILTIN (defn, true, true, false, 1, -1 ) \
|
||
BUILTIN (divert, false, false, false, 0, 2 ) \
|
||
BUILTIN (divnum, false, false, false, 0, 0 ) \
|
||
BUILTIN (dnl, false, false, false, 0, 0 ) \
|
||
BUILTIN (dumpdef, true, false, false, 0, -1 ) \
|
||
BUILTIN (errprint, false, true, false, 1, -1 ) \
|
||
BUILTIN (eval, false, true, true, 1, 3 ) \
|
||
BUILTIN (ifdef, true, true, false, 2, 3 ) \
|
||
BUILTIN (ifelse, true, true, false, 1, -1 ) \
|
||
BUILTIN (include, false, true, false, 1, 1 ) \
|
||
BUILTIN (incr, false, true, true, 1, 1 ) \
|
||
BUILTIN (index, false, true, true, 2, 2 ) \
|
||
BUILTIN (len, false, true, true, 1, 1 ) \
|
||
BUILTIN (m4exit, false, false, false, 0, 1 ) \
|
||
BUILTIN (m4wrap, true, true, false, 1, -1 ) \
|
||
BUILTIN (maketemp, false, true, false, 1, 1 ) \
|
||
BUILTIN (mkstemp, false, true, false, 1, 1 ) \
|
||
BUILTIN (popdef, true, true, false, 1, -1 ) \
|
||
BUILTIN (pushdef, true, true, false, 1, 2 ) \
|
||
BUILTIN (shift, true, true, false, 1, -1 ) \
|
||
BUILTIN (sinclude, false, true, false, 1, 1 ) \
|
||
BUILTIN (substr, false, true, true, 2, 3 ) \
|
||
BUILTIN (syscmd, false, true, true, 1, 1 ) \
|
||
BUILTIN (sysval, false, false, false, 0, 0 ) \
|
||
BUILTIN (traceoff, true, false, false, 0, -1 ) \
|
||
BUILTIN (traceon, true, false, false, 0, -1 ) \
|
||
BUILTIN (translit, false, true, true, 2, 3 ) \
|
||
BUILTIN (undefine, true, true, false, 1, -1 ) \
|
||
BUILTIN (undivert, false, false, false, 0, -1 ) \
|
||
|
||
|
||
typedef intmax_t number;
|
||
typedef uintmax_t unumber;
|
||
|
||
static int dumpdef_cmp_CB (const void *s1, const void *s2);
|
||
static void * dump_symbol_CB (m4_symbol_table *ignored, const char *name,
|
||
m4_symbol *symbol, void *userdata);
|
||
static const char *ntoa (number value, int radix);
|
||
static void numb_obstack (m4_obstack *obs, number value,
|
||
int radix, int min);
|
||
|
||
|
||
/* Generate prototypes for each builtin handler function. */
|
||
#define BUILTIN(handler, macros, blind, side, min, max) M4BUILTIN (handler)
|
||
builtin_functions
|
||
#undef BUILTIN
|
||
|
||
|
||
/* Generate a table for mapping m4 symbol names to handler functions. */
|
||
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 },
|
||
};
|
||
|
||
|
||
|
||
/* This module cannot be safely unloaded from memory, incase the unload
|
||
is triggered by m4exit, and the module is removed while m4exit is in
|
||
progress. */
|
||
M4INIT_HANDLER (m4)
|
||
{
|
||
const char *err = m4_module_makeresident (module);
|
||
if (err)
|
||
m4_error (context, 0, 0, NULL, _("cannot make module `%s' resident: %s"),
|
||
m4_get_module_name (module), err);
|
||
}
|
||
|
||
|
||
|
||
/* The rest of this file is code for builtins and expansion of user
|
||
defined macros. All the functions for builtins have a prototype as:
|
||
|
||
void builtin_MACRONAME (m4_obstack *obs, int argc, char *argv[]);
|
||
|
||
The function are expected to leave their expansion on the obstack OBS,
|
||
as an unfinished object. ARGV is a table of ARGC pointers to the
|
||
individual arguments to the macro. Please note that in general
|
||
argv[argc] != NULL. */
|
||
|
||
M4BUILTIN_HANDLER (define)
|
||
{
|
||
if (m4_is_arg_text (argv, 1))
|
||
{
|
||
m4_symbol_value *value = m4_symbol_value_create ();
|
||
|
||
if (m4_symbol_value_copy (context, value, m4_arg_symbol (argv, 2)))
|
||
m4_warn (context, 0, M4ARG (0), _("cannot concatenate builtins"));
|
||
m4_symbol_define (M4SYMTAB, M4ARG (1), value);
|
||
}
|
||
else
|
||
m4_warn (context, 0, M4ARG (0), _("invalid macro name ignored"));
|
||
}
|
||
|
||
M4BUILTIN_HANDLER (undefine)
|
||
{
|
||
const char *me = M4ARG (0);
|
||
size_t i;
|
||
for (i = 1; i < argc; i++)
|
||
if (m4_symbol_value_lookup (context, me, argv, i, true))
|
||
m4_symbol_delete (M4SYMTAB, M4ARG (i));
|
||
}
|
||
|
||
M4BUILTIN_HANDLER (pushdef)
|
||
{
|
||
if (m4_is_arg_text (argv, 1))
|
||
{
|
||
m4_symbol_value *value = m4_symbol_value_create ();
|
||
|
||
if (m4_symbol_value_copy (context, value, m4_arg_symbol (argv, 2)))
|
||
m4_warn (context, 0, M4ARG (0), _("cannot concatenate builtins"));
|
||
m4_symbol_pushdef (M4SYMTAB, M4ARG (1), value);
|
||
}
|
||
else
|
||
m4_warn (context, 0, M4ARG (0), _("invalid macro name ignored"));
|
||
}
|
||
|
||
M4BUILTIN_HANDLER (popdef)
|
||
{
|
||
const char *me = M4ARG (0);
|
||
size_t i;
|
||
for (i = 1; i < argc; i++)
|
||
if (m4_symbol_value_lookup (context, me, argv, i, true))
|
||
m4_symbol_popdef (M4SYMTAB, M4ARG (i));
|
||
}
|
||
|
||
|
||
|
||
/* --- CONDITIONALS OF M4 --- */
|
||
|
||
|
||
M4BUILTIN_HANDLER (ifdef)
|
||
{
|
||
m4_push_arg (context, obs, argv,
|
||
(m4_symbol_value_lookup (context, M4ARG (0), argv, 1, false)
|
||
? 2 : 3));
|
||
}
|
||
|
||
M4BUILTIN_HANDLER (ifelse)
|
||
{
|
||
const char *me = M4ARG (0);
|
||
size_t i;
|
||
|
||
/* The valid ranges of argc for ifelse is discontinuous, we cannot
|
||
rely on the regular mechanisms. */
|
||
if (argc == 2 || m4_bad_argc (context, argc, me, 3, -1, false))
|
||
return;
|
||
else if (argc % 3 == 0)
|
||
/* Diagnose excess arguments if 5, 8, 11, etc., actual arguments. */
|
||
m4_bad_argc (context, argc, me, 0, argc - 2, false);
|
||
|
||
i = 1;
|
||
argc--;
|
||
|
||
while (true)
|
||
{
|
||
if (m4_arg_equal (context, argv, i, i + 1))
|
||
{
|
||
m4_push_arg (context, obs, argv, i + 2);
|
||
return;
|
||
}
|
||
switch (argc)
|
||
{
|
||
case 3:
|
||
return;
|
||
|
||
case 4:
|
||
case 5:
|
||
m4_push_arg (context, obs, argv, i + 3);
|
||
return;
|
||
|
||
default:
|
||
argc -= 3;
|
||
i += 3;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/* qsort comparison routine, for sorting the table made in m4_dumpdef (). */
|
||
static int
|
||
dumpdef_cmp_CB (const void *s1, const void *s2)
|
||
{
|
||
return strcmp (*(const char **) s1, *(const char **) s2);
|
||
}
|
||
|
||
/* The function m4_dump_symbols () is for use by "dumpdef". It builds up a
|
||
table of all defined symbol names. */
|
||
static void *
|
||
dump_symbol_CB (m4_symbol_table *ignored, const char *name, m4_symbol *symbol,
|
||
void *userdata)
|
||
{
|
||
m4_dump_symbol_data *symbol_data = (m4_dump_symbol_data *) userdata;
|
||
|
||
assert (name);
|
||
assert (symbol);
|
||
assert (!m4_is_symbol_value_void (m4_get_symbol_value (symbol)));
|
||
|
||
if (symbol_data->size == 0)
|
||
{
|
||
obstack_ptr_grow (symbol_data->obs, name);
|
||
symbol_data->size = (obstack_room (symbol_data->obs)
|
||
/ sizeof (const char *));
|
||
}
|
||
else
|
||
{
|
||
obstack_ptr_grow_fast (symbol_data->obs, name);
|
||
symbol_data->size--;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
/* If there are no arguments, build a sorted list of all defined
|
||
symbols, otherwise, only the specified symbols. */
|
||
void
|
||
m4_dump_symbols (m4 *context, m4_dump_symbol_data *data, size_t argc,
|
||
m4_macro_args *argv, bool complain)
|
||
{
|
||
const char *me = M4ARG (0);
|
||
assert (obstack_object_size (data->obs) == 0);
|
||
data->size = obstack_room (data->obs) / sizeof (const char *);
|
||
|
||
if (argc == 1)
|
||
m4_symtab_apply (M4SYMTAB, false, dump_symbol_CB, data);
|
||
else
|
||
{
|
||
size_t i;
|
||
m4_symbol *symbol;
|
||
|
||
for (i = 1; i < argc; i++)
|
||
{
|
||
symbol = m4_symbol_value_lookup (context, me, argv, i, complain);
|
||
if (symbol)
|
||
dump_symbol_CB (NULL, M4ARG (i), symbol, data);
|
||
}
|
||
}
|
||
|
||
data->size = obstack_object_size (data->obs) / sizeof (const char *);
|
||
data->base = (const char **) obstack_finish (data->obs);
|
||
qsort (data->base, data->size, sizeof (const char *), dumpdef_cmp_CB);
|
||
}
|
||
|
||
|
||
/* Implementation of "dumpdef" itself. It builds up a table of pointers to
|
||
symbols, sorts it and prints the sorted table. */
|
||
M4BUILTIN_HANDLER (dumpdef)
|
||
{
|
||
m4_dump_symbol_data data;
|
||
const m4_string_pair *quotes = NULL;
|
||
bool stack = m4_is_debug_bit (context, M4_DEBUG_TRACE_STACK);
|
||
size_t arg_length = m4_get_max_debug_arg_length_opt (context);
|
||
bool module = m4_is_debug_bit (context, M4_DEBUG_TRACE_MODULE);
|
||
|
||
if (m4_is_debug_bit (context, M4_DEBUG_TRACE_QUOTE))
|
||
quotes = m4_get_syntax_quotes (M4SYNTAX);
|
||
data.obs = m4_arg_scratch (context);
|
||
m4_dump_symbols (context, &data, argc, argv, true);
|
||
|
||
for (; data.size > 0; --data.size, data.base++)
|
||
{
|
||
m4_symbol *symbol = m4_symbol_lookup (M4SYMTAB, data.base[0]);
|
||
assert (symbol);
|
||
|
||
obstack_grow (obs, data.base[0], strlen (data.base[0]));
|
||
obstack_1grow (obs, ':');
|
||
obstack_1grow (obs, '\t');
|
||
m4_symbol_print (context, symbol, obs, quotes, stack, arg_length,
|
||
module);
|
||
obstack_1grow (obs, '\n');
|
||
}
|
||
|
||
obstack_1grow (obs, '\0');
|
||
m4_sysval_flush (context, false);
|
||
fputs ((char *) obstack_finish (obs), stderr);
|
||
}
|
||
|
||
/* The macro "defn" returns the quoted definition of the macro named by
|
||
the first argument. If the macro is builtin, it will push a special
|
||
macro-definition token on the input stack. */
|
||
M4BUILTIN_HANDLER (defn)
|
||
{
|
||
const char *me = M4ARG (0);
|
||
size_t i;
|
||
|
||
for (i = 1; i < argc; i++)
|
||
{
|
||
m4_symbol *symbol = m4_symbol_value_lookup (context, me, argv, i, true);
|
||
|
||
if (!symbol)
|
||
;
|
||
else if (m4_is_symbol_text (symbol))
|
||
m4_shipout_string (context, obs, m4_get_symbol_text (symbol),
|
||
m4_get_symbol_len (symbol), true);
|
||
else if (m4_is_symbol_func (symbol))
|
||
m4_push_builtin (context, obs, m4_get_symbol_value (symbol));
|
||
else if (m4_is_symbol_placeholder (symbol))
|
||
m4_warn (context, 0, M4ARG (i),
|
||
_("builtin `%s' requested by frozen file not found"),
|
||
m4_get_symbol_placeholder (symbol));
|
||
else
|
||
{
|
||
assert (!"Bad token data type in m4_defn");
|
||
abort ();
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/* This section contains macros to handle the builtins "syscmd"
|
||
and "sysval". */
|
||
|
||
/* Exit code from last "syscmd" command. */
|
||
/* FIXME - we should preserve this value across freezing. See
|
||
http://lists.gnu.org/archive/html/bug-m4/2006-06/msg00059.html
|
||
for ideas on how do to that. */
|
||
static int m4_sysval = 0;
|
||
|
||
/* Helper macros for readability. */
|
||
#if UNIX || defined WEXITSTATUS
|
||
# define M4_SYSVAL_EXITBITS(status) \
|
||
(WIFEXITED (status) ? WEXITSTATUS (status) : 0)
|
||
# define M4_SYSVAL_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 M4_SYSVAL_EXITBITS(status) status
|
||
# define M4_SYSVAL_TERMSIGBITS(status) 0
|
||
#endif /* !UNIX && !defined WEXITSTATUS */
|
||
|
||
/* Fallback definitions if <stdlib.h> or <sys/wait.h> are inadequate. */
|
||
/* FIXME - this may fit better as a gnulib module. */
|
||
#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
|
||
|
||
void
|
||
m4_set_sysval (int value)
|
||
{
|
||
m4_sysval = value;
|
||
}
|
||
|
||
/* Flush a given output STREAM. If REPORT, also print an error
|
||
message and clear the stream error bit. */
|
||
static void
|
||
sysval_flush_helper (m4 *context, FILE *stream, bool report)
|
||
{
|
||
if (fflush (stream) == EOF && report)
|
||
{
|
||
m4_error (context, 0, errno, NULL, _("write error"));
|
||
clearerr (stream);
|
||
}
|
||
}
|
||
|
||
/* Flush all user output streams, prior to doing something that can
|
||
could lose unflushed data or interleave debug and normal output
|
||
incorrectly. If REPORT, then print an error message on failure and
|
||
clear the stream error bit; otherwise a subsequent ferror can track
|
||
that an error occurred. */
|
||
void
|
||
m4_sysval_flush (m4 *context, bool report)
|
||
{
|
||
FILE *debug_file = m4_get_debug_file (context);
|
||
|
||
if (debug_file != stdout)
|
||
sysval_flush_helper (context, stdout, report);
|
||
if (debug_file != stderr)
|
||
/* If we have problems with stderr, we can't really report that
|
||
problem to stderr. The closeout module will ensure the exit
|
||
status reflects the problem, though. */
|
||
fflush (stderr);
|
||
if (debug_file != NULL)
|
||
sysval_flush_helper (context, debug_file, report);
|
||
/* POSIX requires that if m4 doesn't consume all input, but stdin is
|
||
opened on a seekable file, that the file pointer be left at the
|
||
next character on exit (but places no restrictions on the file
|
||
pointer location on a non-seekable file). It also requires that
|
||
fflush() followed by fseeko() on an input file set the underlying
|
||
file pointer, and gnulib guarantees these semantics. However,
|
||
fflush() on a non-seekable file can lose buffered data, which we
|
||
might otherwise want to process after syscmd. Hence, we must
|
||
check whether stdin is seekable. We must also be tolerant of
|
||
operating with stdin closed, so we don't report any failures in
|
||
this attempt. The stdio-safer module and friends are essential,
|
||
so that if stdin was closed, this lseek is not on some other file
|
||
that we have since opened. */
|
||
if (lseek (STDIN_FILENO, 0, SEEK_CUR) >= 0
|
||
&& fflush (stdin) == 0)
|
||
{
|
||
fseeko (stdin, 0, SEEK_CUR);
|
||
}
|
||
}
|
||
|
||
M4BUILTIN_HANDLER (syscmd)
|
||
{
|
||
if (m4_get_safer_opt (context))
|
||
{
|
||
m4_error (context, 0, 0, M4ARG (0), _("disabled by --safer"));
|
||
return;
|
||
}
|
||
|
||
/* Optimize the empty command. */
|
||
if (m4_arg_empty (argv, 1))
|
||
{
|
||
m4_set_sysval (0);
|
||
return;
|
||
}
|
||
m4_sysval_flush (context, false);
|
||
m4_sysval = system (M4ARG (1));
|
||
/* FIXME - determine if libtool works for OS/2, in which case the
|
||
FUNC_SYSTEM_BROKEN section on the branch must be ported to work
|
||
around the bug in their EMX libc system(). */
|
||
}
|
||
|
||
|
||
M4BUILTIN_HANDLER (sysval)
|
||
{
|
||
m4_shipout_int (obs, (m4_sysval == -1 ? 127
|
||
: (M4_SYSVAL_EXITBITS (m4_sysval)
|
||
| M4_SYSVAL_TERMSIGBITS (m4_sysval))));
|
||
}
|
||
|
||
|
||
M4BUILTIN_HANDLER (incr)
|
||
{
|
||
int value;
|
||
|
||
if (!m4_numeric_arg (context, M4ARG (0), M4ARG (1), &value))
|
||
return;
|
||
|
||
m4_shipout_int (obs, value + 1);
|
||
}
|
||
|
||
M4BUILTIN_HANDLER (decr)
|
||
{
|
||
int value;
|
||
|
||
if (!m4_numeric_arg (context, M4ARG (0), M4ARG (1), &value))
|
||
return;
|
||
|
||
m4_shipout_int (obs, value - 1);
|
||
}
|
||
|
||
|
||
/* This section contains the macros "divert", "undivert" and "divnum" for
|
||
handling diversion. The utility functions used lives in output.c. */
|
||
|
||
/* Divert further output to the diversion given by ARGV[1]. Out of range
|
||
means discard further output. */
|
||
M4BUILTIN_HANDLER (divert)
|
||
{
|
||
int i = 0;
|
||
|
||
if (argc >= 2 && !m4_numeric_arg (context, M4ARG (0), M4ARG (1), &i))
|
||
return;
|
||
m4_make_diversion (context, i);
|
||
m4_divert_text (context, NULL, M4ARG (2), M4ARGLEN (2),
|
||
m4_get_current_line (context));
|
||
}
|
||
|
||
/* Expand to the current diversion number. */
|
||
M4BUILTIN_HANDLER (divnum)
|
||
{
|
||
m4_shipout_int (obs, m4_get_current_diversion (context));
|
||
}
|
||
|
||
/* Bring back the diversion given by the argument list. If none is
|
||
specified, bring back all diversions. GNU specific is the option
|
||
of undiverting the named file, by passing a non-numeric argument to
|
||
undivert (). */
|
||
|
||
M4BUILTIN_HANDLER (undivert)
|
||
{
|
||
size_t i = 0;
|
||
const char *me = M4ARG (0);
|
||
|
||
if (argc == 1)
|
||
m4_undivert_all (context);
|
||
else
|
||
for (i = 1; i < argc; i++)
|
||
{
|
||
const char *str = M4ARG (i);
|
||
char *endp;
|
||
int diversion = strtol (str, &endp, 10);
|
||
if (*endp == '\0' && !isspace ((unsigned char) *str))
|
||
m4_insert_diversion (context, diversion);
|
||
else if (m4_get_posixly_correct_opt (context))
|
||
m4_numeric_arg (context, me, str, &diversion);
|
||
else
|
||
{
|
||
char *filepath = m4_path_search (context, str, NULL);
|
||
FILE *fp = m4_fopen (context, filepath);
|
||
|
||
free (filepath);
|
||
if (fp != NULL)
|
||
{
|
||
m4_insert_file (context, fp);
|
||
if (fclose (fp) == EOF)
|
||
m4_error (context, 0, errno, me, _("error undiverting `%s'"),
|
||
str);
|
||
}
|
||
else
|
||
m4_error (context, 0, errno, me, _("cannot undivert `%s'"), str);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/* This section contains various macros, which does not fall into
|
||
any specific group. These are "dnl", "shift", "changequote",
|
||
"changecom" and "changesyntax" */
|
||
|
||
/* Delete all subsequent whitespace from input. The function skip_line ()
|
||
lives in input.c. */
|
||
M4BUILTIN_HANDLER (dnl)
|
||
{
|
||
m4_skip_line (context, M4ARG (0));
|
||
}
|
||
|
||
/* Shift all arguments one to the left, discarding the first argument.
|
||
Each output argument is quoted with the current quotes. */
|
||
M4BUILTIN_HANDLER (shift)
|
||
{
|
||
m4_push_args (context, obs, argv, true, true);
|
||
}
|
||
|
||
/* Change the current quotes. The function set_quotes () lives in
|
||
syntax.c. */
|
||
M4BUILTIN_HANDLER (changequote)
|
||
{
|
||
m4_set_quotes (M4SYNTAX,
|
||
(argc >= 2) ? M4ARG (1) : NULL,
|
||
(argc >= 3) ? M4ARG (2) : NULL);
|
||
}
|
||
|
||
/* Change the current comment delimiters. The function set_comment ()
|
||
lives in syntax.c. */
|
||
M4BUILTIN_HANDLER (changecom)
|
||
{
|
||
m4_set_comment (M4SYNTAX,
|
||
(argc >= 2) ? M4ARG (1) : NULL,
|
||
(argc >= 3) ? M4ARG (2) : NULL);
|
||
}
|
||
|
||
|
||
/* 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. */
|
||
|
||
/* Include a file, complaining in case of errors. */
|
||
M4BUILTIN_HANDLER (include)
|
||
{
|
||
m4_load_filename (context, M4ARG (0), M4ARG (1), obs, false);
|
||
}
|
||
|
||
/* Include a file, ignoring errors. */
|
||
M4BUILTIN_HANDLER (sinclude)
|
||
{
|
||
m4_load_filename (context, M4ARG (0), M4ARG (1), obs, true);
|
||
}
|
||
|
||
|
||
/* More miscellaneous builtins -- "maketemp", "errprint". */
|
||
|
||
/* Add trailing `X' to PATTERN of length LEN as necessary, then
|
||
securely create the temporary file system object. If DIR, create a
|
||
directory instead of a file. Report errors on behalf of MACRO. If
|
||
successful, output the quoted resulting name on OBS. */
|
||
void
|
||
m4_make_temp (m4 *context, m4_obstack *obs, const char *macro,
|
||
const char *pattern, size_t len, bool dir)
|
||
{
|
||
int fd;
|
||
int i;
|
||
char *name;
|
||
const m4_string_pair *quotes = m4_get_syntax_quotes (M4SYNTAX);
|
||
|
||
if (m4_get_safer_opt (context))
|
||
{
|
||
m4_error (context, 0, 0, macro, _("disabled by --safer"));
|
||
return;
|
||
}
|
||
|
||
/* Guarantee that there are six trailing 'X' characters, even if the
|
||
user forgot to supply them. Output must be quoted if
|
||
successful. */
|
||
assert (obstack_object_size (obs) == 0);
|
||
obstack_grow (obs, quotes->str1, quotes->len1);
|
||
obstack_grow (obs, pattern, len);
|
||
for (i = 0; len > 0 && i < 6; i++)
|
||
if (pattern[--len] != 'X')
|
||
break;
|
||
obstack_grow0 (obs, "XXXXXX", 6 - i);
|
||
name = (char *) obstack_base (obs) + quotes->len1;
|
||
|
||
/* Make the temporary object. */
|
||
errno = 0;
|
||
fd = gen_tempname (name, dir ? GT_DIR : GT_FILE);
|
||
if (fd < 0)
|
||
{
|
||
/* This use of _() will need to change if xgettext ever changes
|
||
its undocumented behavior of parsing both string options. */
|
||
|
||
m4_error (context, 0, errno, macro,
|
||
_(dir ? "cannot create directory from template `%s'"
|
||
: "cannot create file from template `%s'"),
|
||
pattern);
|
||
obstack_free (obs, obstack_finish (obs));
|
||
}
|
||
else
|
||
{
|
||
if (!dir)
|
||
close (fd);
|
||
/* Remove NUL, then finish quote. */
|
||
obstack_blank (obs, -1);
|
||
obstack_grow (obs, quotes->str2, quotes->len2);
|
||
}
|
||
}
|
||
|
||
/* Use the first argument as at template for a temporary file name. */
|
||
M4BUILTIN_HANDLER (maketemp)
|
||
{
|
||
m4_warn (context, 0, M4ARG (0), _("recommend using mkstemp instead"));
|
||
if (m4_get_posixly_correct_opt (context))
|
||
{
|
||
/* POSIX states "any trailing 'X' characters [are] replaced with
|
||
the current process ID as a string", without referencing the
|
||
file system. Horribly insecure, but we have to do it.
|
||
|
||
For reference, Solaris m4 does:
|
||
maketemp() -> `'
|
||
maketemp(X) -> `X'
|
||
maketemp(XX) -> `Xn', where n is last digit of pid
|
||
maketemp(XXXXXXXX) -> `X00nnnnn', where nnnnn is 16-bit pid
|
||
*/
|
||
const char *str = M4ARG (1);
|
||
size_t len = M4ARGLEN (1);
|
||
size_t i;
|
||
size_t len2;
|
||
|
||
for (i = len; i > 1; i--)
|
||
if (str[i - 1] != 'X')
|
||
break;
|
||
obstack_grow (obs, str, i);
|
||
str = ntoa ((number) getpid (), 10);
|
||
len2 = strlen (str);
|
||
if (len2 > len - i)
|
||
obstack_grow (obs, str + len2 - (len - i), len - i);
|
||
else
|
||
{
|
||
while (i++ < len - len2)
|
||
obstack_1grow (obs, '0');
|
||
obstack_grow (obs, str, len2);
|
||
}
|
||
}
|
||
else
|
||
m4_make_temp (context, obs, M4ARG (0), M4ARG (1), M4ARGLEN (1), false);
|
||
}
|
||
|
||
/* Use the first argument as a template for a temporary file name. */
|
||
M4BUILTIN_HANDLER (mkstemp)
|
||
{
|
||
m4_make_temp (context, obs, M4ARG (0), M4ARG (1), M4ARGLEN (1), false);
|
||
}
|
||
|
||
/* Print all arguments on standard error. */
|
||
M4BUILTIN_HANDLER (errprint)
|
||
{
|
||
size_t i;
|
||
|
||
m4_sysval_flush (context, false);
|
||
/* The close_stdin module makes it safe to skip checking the return
|
||
values here. */
|
||
fwrite (M4ARG (1), 1, M4ARGLEN (1), stderr);
|
||
for (i = 2; i < m4_arg_argc (argv); i++)
|
||
{
|
||
fputc (' ', stderr);
|
||
fwrite (M4ARG (i), 1, M4ARGLEN (i), stderr);
|
||
}
|
||
fflush (stderr);
|
||
}
|
||
|
||
|
||
/* This section contains various macros for exiting, saving input until
|
||
EOF is seen, and tracing macro calls. That is: "m4exit", "m4wrap",
|
||
"traceon" and "traceoff". */
|
||
|
||
/* Exit immediately, with exitcode specified by the first argument, 0 if no
|
||
arguments are present. */
|
||
M4BUILTIN_HANDLER (m4exit)
|
||
{
|
||
const char *me = M4ARG (0);
|
||
int exit_code = EXIT_SUCCESS;
|
||
|
||
/* Warn on bad arguments, but still exit. */
|
||
if (argc >= 2 && !m4_numeric_arg (context, me, M4ARG (1), &exit_code))
|
||
exit_code = EXIT_FAILURE;
|
||
if (exit_code < 0 || exit_code > 255)
|
||
{
|
||
m4_warn (context, 0, me, _("exit status out of range: `%d'"), exit_code);
|
||
exit_code = EXIT_FAILURE;
|
||
}
|
||
|
||
/* Ensure that atexit handlers see correct nonzero status. */
|
||
if (exit_code != EXIT_SUCCESS)
|
||
m4_set_exit_failure (exit_code);
|
||
|
||
/* Ensure any module exit callbacks are executed. */
|
||
m4__module_exit (context);
|
||
|
||
/* Change debug stream back to stderr, to force flushing debug
|
||
stream and detect any errors. */
|
||
m4_debug_set_output (context, me, NULL);
|
||
m4_sysval_flush (context, true);
|
||
|
||
/* Check for saved error. */
|
||
if (exit_code == 0 && m4_get_exit_status (context) != 0)
|
||
exit_code = m4_get_exit_status (context);
|
||
exit (exit_code);
|
||
}
|
||
|
||
/* Save the argument text until EOF has been seen, allowing for user
|
||
specified cleanup action. GNU version saves all arguments, the standard
|
||
version only the first. */
|
||
M4BUILTIN_HANDLER (m4wrap)
|
||
{
|
||
m4_wrap_args (context, argv);
|
||
}
|
||
|
||
/* Enable tracing of all specified macros, or all, if none is specified.
|
||
Tracing is disabled by default, when a macro is defined. This can be
|
||
overridden by the "t" debug flag. */
|
||
|
||
M4BUILTIN_HANDLER (traceon)
|
||
{
|
||
const char *me = M4ARG (0);
|
||
size_t i;
|
||
|
||
if (argc == 1)
|
||
m4_set_debug_level_opt (context, (m4_get_debug_level_opt (context)
|
||
| M4_DEBUG_TRACE_ALL));
|
||
else
|
||
for (i = 1; i < argc; i++)
|
||
if (m4_is_arg_text (argv, i))
|
||
m4_set_symbol_name_traced (M4SYMTAB, M4ARG (i), true);
|
||
else
|
||
m4_warn (context, 0, me, _("invalid macro name ignored"));
|
||
}
|
||
|
||
/* Disable tracing of all specified macros, or all, if none is specified. */
|
||
M4BUILTIN_HANDLER (traceoff)
|
||
{
|
||
const char *me = M4ARG (0);
|
||
size_t i;
|
||
|
||
if (argc == 1)
|
||
m4_set_debug_level_opt (context, (m4_get_debug_level_opt (context)
|
||
& ~M4_DEBUG_TRACE_ALL));
|
||
else
|
||
for (i = 1; i < argc; i++)
|
||
if (m4_is_arg_text (argv, i))
|
||
m4_set_symbol_name_traced (M4SYMTAB, M4ARG (i), false);
|
||
else
|
||
m4_warn (context, 0, me, _("invalid macro name ignored"));
|
||
}
|
||
|
||
|
||
/* This section contains text processing macros: "len", "index",
|
||
"substr", "translit", "format", "regexp" and "patsubst". The last
|
||
three are GNU specific. */
|
||
|
||
/* Expand to the length of the first argument. */
|
||
M4BUILTIN_HANDLER (len)
|
||
{
|
||
m4_shipout_int (obs, M4ARGLEN (1));
|
||
}
|
||
|
||
/* The macro expands to the first index of the second argument in the first
|
||
argument. */
|
||
M4BUILTIN_HANDLER (index)
|
||
{
|
||
const char *haystack = M4ARG (1);
|
||
const char *needle = M4ARG (2);
|
||
const char *result = NULL;
|
||
int retval = -1;
|
||
|
||
/* Rely on the optimizations guaranteed by gnulib's memmem
|
||
module. */
|
||
result = (char *) memmem (haystack, M4ARGLEN (1), needle, M4ARGLEN (2));
|
||
if (result)
|
||
retval = result - haystack;
|
||
|
||
m4_shipout_int (obs, retval);
|
||
}
|
||
|
||
/* The macro "substr" extracts substrings from the first argument, starting
|
||
from the index given by the second argument, extending for a length
|
||
given by the third argument. If the third argument is missing, the
|
||
substring extends to the end of the first argument. */
|
||
M4BUILTIN_HANDLER (substr)
|
||
{
|
||
const char *me = M4ARG (0);
|
||
const char *str = M4ARG (1);
|
||
int start = 0;
|
||
int length;
|
||
int avail;
|
||
|
||
if (argc <= 2)
|
||
{
|
||
m4_push_arg (context, obs, argv, 1);
|
||
return;
|
||
}
|
||
|
||
length = avail = M4ARGLEN (1);
|
||
if (!m4_numeric_arg (context, me, M4ARG (2), &start))
|
||
return;
|
||
|
||
if (argc >= 4 && !m4_numeric_arg (context, me, M4ARG (3), &length))
|
||
return;
|
||
|
||
if (start < 0 || length <= 0 || start >= avail)
|
||
return;
|
||
|
||
if (start + length > avail)
|
||
length = avail - start;
|
||
obstack_grow (obs, str + start, length);
|
||
}
|
||
|
||
|
||
/* Ranges are expanded by 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. */
|
||
const char *
|
||
m4_expand_ranges (const char *s, m4_obstack *obs)
|
||
{
|
||
unsigned char from;
|
||
unsigned char to;
|
||
|
||
assert (obstack_object_size (obs) == 0);
|
||
for (from = '\0'; *s != '\0'; from = *s++)
|
||
{
|
||
if (*s == '-' && from != '\0')
|
||
{
|
||
to = *++s;
|
||
if (to == '\0')
|
||
{
|
||
/* trailing dash */
|
||
obstack_1grow (obs, '-');
|
||
break;
|
||
}
|
||
else if (from <= to)
|
||
{
|
||
while (from++ < to)
|
||
obstack_1grow (obs, from);
|
||
}
|
||
else
|
||
{
|
||
while (--from >= to)
|
||
obstack_1grow (obs, from);
|
||
}
|
||
}
|
||
else
|
||
obstack_1grow (obs, *s);
|
||
}
|
||
obstack_1grow (obs, '\0');
|
||
return obstack_finish (obs);
|
||
}
|
||
|
||
/* The macro "translit" translates all characters in the first
|
||
argument, which are present in the second argument, into the
|
||
corresponding character from the third argument. If the third
|
||
argument is shorter than the second, the extra characters in the
|
||
second argument are deleted from the first. */
|
||
M4BUILTIN_HANDLER (translit)
|
||
{
|
||
const char *data;
|
||
const char *from;
|
||
const char *to;
|
||
char map[UCHAR_MAX + 1] = {0};
|
||
char found[UCHAR_MAX + 1] = {0};
|
||
unsigned char ch;
|
||
|
||
if (argc <= 2)
|
||
{
|
||
m4_push_arg (context, obs, argv, 1);
|
||
return;
|
||
}
|
||
|
||
from = M4ARG (2);
|
||
if (strchr (from, '-') != NULL)
|
||
{
|
||
from = m4_expand_ranges (from, m4_arg_scratch (context));
|
||
assert (from);
|
||
}
|
||
|
||
to = M4ARG (3);
|
||
if (strchr (to, '-') != NULL)
|
||
{
|
||
to = m4_expand_ranges (to, m4_arg_scratch (context));
|
||
assert (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. */
|
||
for ( ; (ch = *from) != '\0'; from++)
|
||
{
|
||
if (!found[ch])
|
||
{
|
||
found[ch] = 1;
|
||
map[ch] = *to;
|
||
}
|
||
if (*to != '\0')
|
||
to++;
|
||
}
|
||
|
||
for (data = M4ARG (1); (ch = *data) != '\0'; data++)
|
||
{
|
||
if (!found[ch])
|
||
obstack_1grow (obs, ch);
|
||
else if (map[ch])
|
||
obstack_1grow (obs, map[ch]);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/* The rest of this file contains the functions to evaluate integer
|
||
* expressions for the "eval" macro. `number' should be at least 32 bits.
|
||
*/
|
||
#define numb_set(ans, x) ((ans) = (x))
|
||
#define numb_set_si(ans, si) (*(ans) = (number) (si))
|
||
|
||
#define numb_ZERO ((number) 0)
|
||
#define numb_ONE ((number) 1)
|
||
|
||
#define numb_init(x) ((x) = numb_ZERO)
|
||
#define numb_fini(x)
|
||
|
||
#define numb_incr(n) ((n) += numb_ONE)
|
||
#define numb_decr(n) ((n) -= numb_ONE)
|
||
|
||
#define numb_zerop(x) ((x) == numb_ZERO)
|
||
#define numb_positivep(x) ((x) > numb_ZERO)
|
||
#define numb_negativep(x) ((x) < numb_ZERO)
|
||
|
||
#define numb_eq(x, y) ((x) = ((x) == (y)))
|
||
#define numb_ne(x, y) ((x) = ((x) != (y)))
|
||
#define numb_lt(x, y) ((x) = ((x) < (y)))
|
||
#define numb_le(x, y) ((x) = ((x) <= (y)))
|
||
#define numb_gt(x, y) ((x) = ((x) > (y)))
|
||
#define numb_ge(x, y) ((x) = ((x) >= (y)))
|
||
|
||
#define numb_lnot(x) ((x) = (!(x)))
|
||
#define numb_lior(x, y) ((x) = ((x) || (y)))
|
||
#define numb_land(x, y) ((x) = ((x) && (y)))
|
||
|
||
#define numb_not(c, x) (*(x) = ~ *(x))
|
||
#define numb_eor(c, x, y) (*(x) = *(x) ^ *(y))
|
||
#define numb_ior(c, x, y) (*(x) = *(x) | *(y))
|
||
#define numb_and(c, x, y) (*(x) = *(x) & *(y))
|
||
|
||
#define numb_plus(x, y) ((x) = ((x) + (y)))
|
||
#define numb_minus(x, y) ((x) = ((x) - (y)))
|
||
#define numb_negate(x) ((x) = (- (x)))
|
||
|
||
#define numb_times(x, y) ((x) = ((x) * (y)))
|
||
/* Be careful of x86 SIGFPE. */
|
||
#define numb_ratio(x, y) \
|
||
(((y) == -1) ? (numb_negate (x)) : ((x) /= (y)))
|
||
#define numb_divide(x, y) \
|
||
((*(y) == -1) ? (numb_negate (*(y))) : (*(x) /= *(y)))
|
||
#define numb_modulo(c, x, y) \
|
||
((*(y) == -1) ? (*(x) = numb_ZERO) : (*(x) %= *(y)))
|
||
/* numb_invert is only used in the context of x**-y, which integral math
|
||
does not support. */
|
||
#define numb_invert(x) return NEGATIVE_EXPONENT
|
||
|
||
/* Minimize undefined C behavior (shifting by a negative number,
|
||
shifting by the width or greater, left shift overflow, or right
|
||
shift of a negative number). Implement Java wrap-around semantics,
|
||
with implicit masking of shift amount. This code assumes that the
|
||
implementation-defined overflow when casting unsigned to signed is
|
||
a silent twos-complement wrap-around. */
|
||
#define shift_mask (sizeof (number) * CHAR_BIT - 1)
|
||
#define numb_lshift(c, x, y) \
|
||
(*(x) = (number) ((unumber) *(x) << (*(y) & shift_mask)))
|
||
#define numb_rshift(c, x, y) \
|
||
(*(x) = (number) (*(x) < 0 \
|
||
? ~(~(unumber) *(x) >> (*(y) & shift_mask)) \
|
||
: (unumber) *(x) >> (*(y) & shift_mask)))
|
||
#define numb_urshift(c, x, y) \
|
||
(*(x) = (number) ((unumber) *(x) >> (*(y) & shift_mask)))
|
||
|
||
|
||
/* The function ntoa () converts VALUE to a signed ascii representation in
|
||
radix RADIX. Radix must be between 2 and 36, inclusive. */
|
||
static const char *
|
||
ntoa (number value, int radix)
|
||
{
|
||
/* Digits for number to ascii conversions. */
|
||
static char const ntoa_digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||
|
||
bool negative;
|
||
unumber uvalue;
|
||
/* Sized for radix 2, plus sign and trailing NUL. */
|
||
static char str[sizeof value * CHAR_BIT + 2];
|
||
char *s = &str[sizeof str];
|
||
|
||
*--s = '\0';
|
||
|
||
if (value < 0)
|
||
{
|
||
negative = true;
|
||
uvalue = (unumber) -value;
|
||
}
|
||
else
|
||
{
|
||
negative = false;
|
||
uvalue = (unumber) value;
|
||
}
|
||
|
||
do
|
||
{
|
||
*--s = ntoa_digits[uvalue % radix];
|
||
uvalue /= radix;
|
||
}
|
||
while (uvalue > 0);
|
||
|
||
if (negative)
|
||
*--s = '-';
|
||
return s;
|
||
}
|
||
|
||
static void
|
||
numb_obstack (m4_obstack *obs, number value, int radix, int min)
|
||
{
|
||
const char *s;
|
||
size_t len;
|
||
|
||
if (radix == 1)
|
||
{
|
||
/* FIXME - this code currently depends on undefined behavior. */
|
||
if (value < 0)
|
||
{
|
||
obstack_1grow (obs, '-');
|
||
value = -value;
|
||
}
|
||
while (min-- - value > 0)
|
||
obstack_1grow (obs, '0');
|
||
while (value-- != 0)
|
||
obstack_1grow (obs, '1');
|
||
return;
|
||
}
|
||
|
||
s = ntoa (value, radix);
|
||
|
||
if (*s == '-')
|
||
{
|
||
obstack_1grow (obs, '-');
|
||
s++;
|
||
}
|
||
len = strlen (s);
|
||
for (min -= len; --min >= 0;)
|
||
obstack_1grow (obs, '0');
|
||
|
||
obstack_grow (obs, s, len);
|
||
}
|
||
|
||
|
||
static void
|
||
numb_initialise (void)
|
||
{
|
||
;
|
||
}
|
||
|
||
/* This macro defines the top level code for the "eval" builtin. The
|
||
actual work is done in the function m4_evaluate (), which lives in
|
||
evalparse.c. */
|
||
#define m4_evaluate builtin_eval
|
||
#include "evalparse.c"
|