diff options
author | Anas Nashif <anas.nashif@intel.com> | 2012-11-05 11:10:13 -0800 |
---|---|---|
committer | Anas Nashif <anas.nashif@intel.com> | 2012-11-05 11:10:13 -0800 |
commit | 7be93f2d05131d061bd4790ae33c8d50f50010d7 (patch) | |
tree | 72736c1606f803a92ffa07c9808ffea2521d009e /src | |
download | m4-7be93f2d05131d061bd4790ae33c8d50f50010d7.tar.gz m4-7be93f2d05131d061bd4790ae33c8d50f50010d7.tar.bz2 m4-7be93f2d05131d061bd4790ae33c8d50f50010d7.zip |
Imported Upstream version 1.4.16upstream/1.4.16
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 28 | ||||
-rw-r--r-- | src/Makefile.in | 1351 | ||||
-rw-r--r-- | src/builtin.c | 2257 | ||||
-rw-r--r-- | src/debug.c | 444 | ||||
-rw-r--r-- | src/eval.c | 855 | ||||
-rw-r--r-- | src/format.c | 385 | ||||
-rw-r--r-- | src/freeze.c | 398 | ||||
-rw-r--r-- | src/input.c | 1156 | ||||
-rw-r--r-- | src/m4.c | 688 | ||||
-rw-r--r-- | src/m4.h | 487 | ||||
-rw-r--r-- | src/macro.c | 391 | ||||
-rw-r--r-- | src/output.c | 1017 | ||||
-rw-r--r-- | src/path.c | 205 | ||||
-rw-r--r-- | src/symtab.c | 401 |
14 files changed, 10063 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..b9b809d --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,28 @@ +## Makefile.am - template for generating Makefile via Automake +## +## Copyright (C) 2006-2011 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/>. +## +## This file written by Eric Blake <ebb9@byu.net> + +AUTOMAKE_OPTIONS = nostdinc +AM_CPPFLAGS = -I$(top_srcdir)/lib -I../lib +AM_CFLAGS = $(WARN_CFLAGS) $(WERROR_CFLAGS) +bin_PROGRAMS = m4 +m4_SOURCES = m4.h m4.c builtin.c debug.c eval.c format.c freeze.c input.c \ +macro.c output.c path.c symtab.c +m4_LDADD = ../lib/libm4.a $(LIBM4_LIBDEPS) $(LIBCSTACK) $(LIBTHREAD) diff --git a/src/Makefile.in b/src/Makefile.in new file mode 100644 index 0000000..502d651 --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,1351 @@ +# Makefile.in generated by automake 1.11.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, +# Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +bin_PROGRAMS = m4$(EXEEXT) +subdir = src +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/00gnulib.m4 \ + $(top_srcdir)/m4/alloca.m4 $(top_srcdir)/m4/ansi-c++.m4 \ + $(top_srcdir)/m4/assert.m4 $(top_srcdir)/m4/autobuild.m4 \ + $(top_srcdir)/m4/btowc.m4 $(top_srcdir)/m4/c-stack.m4 \ + $(top_srcdir)/m4/canonicalize.m4 $(top_srcdir)/m4/cloexec.m4 \ + $(top_srcdir)/m4/close-stream.m4 $(top_srcdir)/m4/close.m4 \ + $(top_srcdir)/m4/closein.m4 $(top_srcdir)/m4/closeout.m4 \ + $(top_srcdir)/m4/codeset.m4 $(top_srcdir)/m4/config-h.m4 \ + $(top_srcdir)/m4/configmake.m4 $(top_srcdir)/m4/dirname.m4 \ + $(top_srcdir)/m4/double-slash-root.m4 $(top_srcdir)/m4/dup2.m4 \ + $(top_srcdir)/m4/eealloc.m4 $(top_srcdir)/m4/environ.m4 \ + $(top_srcdir)/m4/errno_h.m4 $(top_srcdir)/m4/error.m4 \ + $(top_srcdir)/m4/execute.m4 $(top_srcdir)/m4/exponentd.m4 \ + $(top_srcdir)/m4/exponentf.m4 $(top_srcdir)/m4/exponentl.m4 \ + $(top_srcdir)/m4/extensions.m4 \ + $(top_srcdir)/m4/fatal-signal.m4 $(top_srcdir)/m4/fclose.m4 \ + $(top_srcdir)/m4/fcntl-o.m4 $(top_srcdir)/m4/fcntl.m4 \ + $(top_srcdir)/m4/fcntl_h.m4 $(top_srcdir)/m4/fflush.m4 \ + $(top_srcdir)/m4/filenamecat.m4 $(top_srcdir)/m4/float_h.m4 \ + $(top_srcdir)/m4/fopen.m4 $(top_srcdir)/m4/fpending.m4 \ + $(top_srcdir)/m4/fpieee.m4 $(top_srcdir)/m4/fpurge.m4 \ + $(top_srcdir)/m4/freading.m4 $(top_srcdir)/m4/frexp.m4 \ + $(top_srcdir)/m4/frexpl.m4 $(top_srcdir)/m4/fseeko.m4 \ + $(top_srcdir)/m4/ftell.m4 $(top_srcdir)/m4/ftello.m4 \ + $(top_srcdir)/m4/getdtablesize.m4 $(top_srcdir)/m4/getopt.m4 \ + $(top_srcdir)/m4/getpagesize.m4 \ + $(top_srcdir)/m4/gettimeofday.m4 $(top_srcdir)/m4/gl_list.m4 \ + $(top_srcdir)/m4/glibc21.m4 $(top_srcdir)/m4/gnulib-common.m4 \ + $(top_srcdir)/m4/gnulib-comp.m4 \ + $(top_srcdir)/m4/include_next.m4 $(top_srcdir)/m4/inline.m4 \ + $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/intmax_t.m4 \ + $(top_srcdir)/m4/inttypes_h.m4 $(top_srcdir)/m4/isnand.m4 \ + $(top_srcdir)/m4/isnanf.m4 $(top_srcdir)/m4/isnanl.m4 \ + $(top_srcdir)/m4/langinfo_h.m4 $(top_srcdir)/m4/lcmessage.m4 \ + $(top_srcdir)/m4/ldexp.m4 $(top_srcdir)/m4/ldexpl.m4 \ + $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \ + $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libsigsegv.m4 \ + $(top_srcdir)/m4/link.m4 $(top_srcdir)/m4/localcharset.m4 \ + $(top_srcdir)/m4/locale-fr.m4 $(top_srcdir)/m4/locale-ja.m4 \ + $(top_srcdir)/m4/locale-tr.m4 $(top_srcdir)/m4/locale-zh.m4 \ + $(top_srcdir)/m4/locale_h.m4 $(top_srcdir)/m4/localename.m4 \ + $(top_srcdir)/m4/lock.m4 $(top_srcdir)/m4/longlong.m4 \ + $(top_srcdir)/m4/lseek.m4 $(top_srcdir)/m4/lstat.m4 \ + $(top_srcdir)/m4/malloc.m4 $(top_srcdir)/m4/malloca.m4 \ + $(top_srcdir)/m4/manywarnings.m4 $(top_srcdir)/m4/math_h.m4 \ + $(top_srcdir)/m4/mbrtowc.m4 $(top_srcdir)/m4/mbsinit.m4 \ + $(top_srcdir)/m4/mbstate_t.m4 $(top_srcdir)/m4/mbtowc.m4 \ + $(top_srcdir)/m4/memchr.m4 $(top_srcdir)/m4/mkdtemp.m4 \ + $(top_srcdir)/m4/mkstemp.m4 $(top_srcdir)/m4/mmap-anon.m4 \ + $(top_srcdir)/m4/mode_t.m4 $(top_srcdir)/m4/multiarch.m4 \ + $(top_srcdir)/m4/nl_langinfo.m4 $(top_srcdir)/m4/nocrash.m4 \ + $(top_srcdir)/m4/open.m4 $(top_srcdir)/m4/pathmax.m4 \ + $(top_srcdir)/m4/pipe2.m4 $(top_srcdir)/m4/posix_spawn.m4 \ + $(top_srcdir)/m4/printf-frexp.m4 \ + $(top_srcdir)/m4/printf-frexpl.m4 $(top_srcdir)/m4/printf.m4 \ + $(top_srcdir)/m4/putenv.m4 $(top_srcdir)/m4/quotearg.m4 \ + $(top_srcdir)/m4/rawmemchr.m4 $(top_srcdir)/m4/readlink.m4 \ + $(top_srcdir)/m4/regex.m4 $(top_srcdir)/m4/rename.m4 \ + $(top_srcdir)/m4/rmdir.m4 $(top_srcdir)/m4/sched_h.m4 \ + $(top_srcdir)/m4/setenv.m4 $(top_srcdir)/m4/setlocale.m4 \ + $(top_srcdir)/m4/sig_atomic_t.m4 $(top_srcdir)/m4/sigaction.m4 \ + $(top_srcdir)/m4/signal_h.m4 \ + $(top_srcdir)/m4/signalblocking.m4 $(top_srcdir)/m4/signbit.m4 \ + $(top_srcdir)/m4/size_max.m4 $(top_srcdir)/m4/snprintf.m4 \ + $(top_srcdir)/m4/spawn-pipe.m4 $(top_srcdir)/m4/spawn_h.m4 \ + $(top_srcdir)/m4/ssize_t.m4 $(top_srcdir)/m4/stat.m4 \ + $(top_srcdir)/m4/stdarg.m4 $(top_srcdir)/m4/stdbool.m4 \ + $(top_srcdir)/m4/stddef_h.m4 $(top_srcdir)/m4/stdint.m4 \ + $(top_srcdir)/m4/stdint_h.m4 $(top_srcdir)/m4/stdio-safer.m4 \ + $(top_srcdir)/m4/stdio_h.m4 $(top_srcdir)/m4/stdlib-safer.m4 \ + $(top_srcdir)/m4/stdlib_h.m4 $(top_srcdir)/m4/strchrnul.m4 \ + $(top_srcdir)/m4/strdup.m4 $(top_srcdir)/m4/strerror.m4 \ + $(top_srcdir)/m4/string_h.m4 $(top_srcdir)/m4/strndup.m4 \ + $(top_srcdir)/m4/strnlen.m4 $(top_srcdir)/m4/strsignal.m4 \ + $(top_srcdir)/m4/strstr.m4 $(top_srcdir)/m4/strtod.m4 \ + $(top_srcdir)/m4/strtol.m4 $(top_srcdir)/m4/symlink.m4 \ + $(top_srcdir)/m4/sys_stat_h.m4 $(top_srcdir)/m4/sys_time_h.m4 \ + $(top_srcdir)/m4/sys_wait_h.m4 $(top_srcdir)/m4/tempname.m4 \ + $(top_srcdir)/m4/threadlib.m4 $(top_srcdir)/m4/time_h.m4 \ + $(top_srcdir)/m4/tls.m4 $(top_srcdir)/m4/tmpdir.m4 \ + $(top_srcdir)/m4/ungetc.m4 $(top_srcdir)/m4/unistd-safer.m4 \ + $(top_srcdir)/m4/unistd_h.m4 $(top_srcdir)/m4/unlocked-io.m4 \ + $(top_srcdir)/m4/vasnprintf.m4 \ + $(top_srcdir)/m4/vasprintf-posix.m4 \ + $(top_srcdir)/m4/vasprintf.m4 $(top_srcdir)/m4/version-etc.m4 \ + $(top_srcdir)/m4/wait-process.m4 $(top_srcdir)/m4/waitpid.m4 \ + $(top_srcdir)/m4/warnings.m4 $(top_srcdir)/m4/wchar_h.m4 \ + $(top_srcdir)/m4/wchar_t.m4 $(top_srcdir)/m4/wcrtomb.m4 \ + $(top_srcdir)/m4/wctob.m4 $(top_srcdir)/m4/wctomb.m4 \ + $(top_srcdir)/m4/wctype_h.m4 $(top_srcdir)/m4/wint_t.m4 \ + $(top_srcdir)/m4/xalloc.m4 $(top_srcdir)/m4/xsize.m4 \ + $(top_srcdir)/m4/xstrndup.m4 $(top_srcdir)/m4/xvasprintf.m4 \ + $(top_srcdir)/acinclude.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/lib/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_m4_OBJECTS = m4.$(OBJEXT) builtin.$(OBJEXT) debug.$(OBJEXT) \ + eval.$(OBJEXT) format.$(OBJEXT) freeze.$(OBJEXT) \ + input.$(OBJEXT) macro.$(OBJEXT) output.$(OBJEXT) \ + path.$(OBJEXT) symtab.$(OBJEXT) +m4_OBJECTS = $(am_m4_OBJECTS) +am__DEPENDENCIES_1 = +m4_DEPENDENCIES = ../lib/libm4.a $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +DEFAULT_INCLUDES = +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +SOURCES = $(m4_SOURCES) +DIST_SOURCES = $(m4_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +pkglibexecdir = @pkglibexecdir@ +ACLOCAL = @ACLOCAL@ +ALLOCA = @ALLOCA@ +ALLOCA_H = @ALLOCA_H@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPLE_UNIVERSAL_BUILD = @APPLE_UNIVERSAL_BUILD@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BITSIZEOF_PTRDIFF_T = @BITSIZEOF_PTRDIFF_T@ +BITSIZEOF_SIG_ATOMIC_T = @BITSIZEOF_SIG_ATOMIC_T@ +BITSIZEOF_SIZE_T = @BITSIZEOF_SIZE_T@ +BITSIZEOF_WCHAR_T = @BITSIZEOF_WCHAR_T@ +BITSIZEOF_WINT_T = @BITSIZEOF_WINT_T@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CONFIG_INCLUDE = @CONFIG_INCLUDE@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CXX_CHOICE = @CXX_CHOICE@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EMULTIHOP_HIDDEN = @EMULTIHOP_HIDDEN@ +EMULTIHOP_VALUE = @EMULTIHOP_VALUE@ +ENOLINK_HIDDEN = @ENOLINK_HIDDEN@ +ENOLINK_VALUE = @ENOLINK_VALUE@ +EOVERFLOW_HIDDEN = @EOVERFLOW_HIDDEN@ +EOVERFLOW_VALUE = @EOVERFLOW_VALUE@ +ERRNO_H = @ERRNO_H@ +EXEEXT = @EXEEXT@ +FLOAT_H = @FLOAT_H@ +GETOPT_H = @GETOPT_H@ +GLIBC21 = @GLIBC21@ +GNULIB_ACOSL = @GNULIB_ACOSL@ +GNULIB_ASINL = @GNULIB_ASINL@ +GNULIB_ATANL = @GNULIB_ATANL@ +GNULIB_ATOLL = @GNULIB_ATOLL@ +GNULIB_BTOWC = @GNULIB_BTOWC@ +GNULIB_CALLOC_POSIX = @GNULIB_CALLOC_POSIX@ +GNULIB_CANONICALIZE_FILE_NAME = @GNULIB_CANONICALIZE_FILE_NAME@ +GNULIB_CEIL = @GNULIB_CEIL@ +GNULIB_CEILF = @GNULIB_CEILF@ +GNULIB_CEILL = @GNULIB_CEILL@ +GNULIB_CHOWN = @GNULIB_CHOWN@ +GNULIB_CLOSE = @GNULIB_CLOSE@ +GNULIB_COSL = @GNULIB_COSL@ +GNULIB_DPRINTF = @GNULIB_DPRINTF@ +GNULIB_DUP2 = @GNULIB_DUP2@ +GNULIB_DUP3 = @GNULIB_DUP3@ +GNULIB_DUPLOCALE = @GNULIB_DUPLOCALE@ +GNULIB_ENVIRON = @GNULIB_ENVIRON@ +GNULIB_EUIDACCESS = @GNULIB_EUIDACCESS@ +GNULIB_EXPL = @GNULIB_EXPL@ +GNULIB_FACCESSAT = @GNULIB_FACCESSAT@ +GNULIB_FCHDIR = @GNULIB_FCHDIR@ +GNULIB_FCHMODAT = @GNULIB_FCHMODAT@ +GNULIB_FCHOWNAT = @GNULIB_FCHOWNAT@ +GNULIB_FCLOSE = @GNULIB_FCLOSE@ +GNULIB_FCNTL = @GNULIB_FCNTL@ +GNULIB_FFLUSH = @GNULIB_FFLUSH@ +GNULIB_FLOOR = @GNULIB_FLOOR@ +GNULIB_FLOORF = @GNULIB_FLOORF@ +GNULIB_FLOORL = @GNULIB_FLOORL@ +GNULIB_FOPEN = @GNULIB_FOPEN@ +GNULIB_FPRINTF = @GNULIB_FPRINTF@ +GNULIB_FPRINTF_POSIX = @GNULIB_FPRINTF_POSIX@ +GNULIB_FPURGE = @GNULIB_FPURGE@ +GNULIB_FPUTC = @GNULIB_FPUTC@ +GNULIB_FPUTS = @GNULIB_FPUTS@ +GNULIB_FREOPEN = @GNULIB_FREOPEN@ +GNULIB_FREXP = @GNULIB_FREXP@ +GNULIB_FREXPL = @GNULIB_FREXPL@ +GNULIB_FSEEK = @GNULIB_FSEEK@ +GNULIB_FSEEKO = @GNULIB_FSEEKO@ +GNULIB_FSTATAT = @GNULIB_FSTATAT@ +GNULIB_FSYNC = @GNULIB_FSYNC@ +GNULIB_FTELL = @GNULIB_FTELL@ +GNULIB_FTELLO = @GNULIB_FTELLO@ +GNULIB_FTRUNCATE = @GNULIB_FTRUNCATE@ +GNULIB_FUTIMENS = @GNULIB_FUTIMENS@ +GNULIB_FWRITE = @GNULIB_FWRITE@ +GNULIB_GETCWD = @GNULIB_GETCWD@ +GNULIB_GETDELIM = @GNULIB_GETDELIM@ +GNULIB_GETDOMAINNAME = @GNULIB_GETDOMAINNAME@ +GNULIB_GETDTABLESIZE = @GNULIB_GETDTABLESIZE@ +GNULIB_GETGROUPS = @GNULIB_GETGROUPS@ +GNULIB_GETHOSTNAME = @GNULIB_GETHOSTNAME@ +GNULIB_GETLINE = @GNULIB_GETLINE@ +GNULIB_GETLOADAVG = @GNULIB_GETLOADAVG@ +GNULIB_GETLOGIN = @GNULIB_GETLOGIN@ +GNULIB_GETLOGIN_R = @GNULIB_GETLOGIN_R@ +GNULIB_GETPAGESIZE = @GNULIB_GETPAGESIZE@ +GNULIB_GETSUBOPT = @GNULIB_GETSUBOPT@ +GNULIB_GETTIMEOFDAY = @GNULIB_GETTIMEOFDAY@ +GNULIB_GETUSERSHELL = @GNULIB_GETUSERSHELL@ +GNULIB_GRANTPT = @GNULIB_GRANTPT@ +GNULIB_ISFINITE = @GNULIB_ISFINITE@ +GNULIB_ISINF = @GNULIB_ISINF@ +GNULIB_ISNAN = @GNULIB_ISNAN@ +GNULIB_ISNAND = @GNULIB_ISNAND@ +GNULIB_ISNANF = @GNULIB_ISNANF@ +GNULIB_ISNANL = @GNULIB_ISNANL@ +GNULIB_ISWBLANK = @GNULIB_ISWBLANK@ +GNULIB_ISWCTYPE = @GNULIB_ISWCTYPE@ +GNULIB_LCHMOD = @GNULIB_LCHMOD@ +GNULIB_LCHOWN = @GNULIB_LCHOWN@ +GNULIB_LDEXPL = @GNULIB_LDEXPL@ +GNULIB_LINK = @GNULIB_LINK@ +GNULIB_LINKAT = @GNULIB_LINKAT@ +GNULIB_LOGB = @GNULIB_LOGB@ +GNULIB_LOGL = @GNULIB_LOGL@ +GNULIB_LSEEK = @GNULIB_LSEEK@ +GNULIB_LSTAT = @GNULIB_LSTAT@ +GNULIB_MALLOC_POSIX = @GNULIB_MALLOC_POSIX@ +GNULIB_MBRLEN = @GNULIB_MBRLEN@ +GNULIB_MBRTOWC = @GNULIB_MBRTOWC@ +GNULIB_MBSCASECMP = @GNULIB_MBSCASECMP@ +GNULIB_MBSCASESTR = @GNULIB_MBSCASESTR@ +GNULIB_MBSCHR = @GNULIB_MBSCHR@ +GNULIB_MBSCSPN = @GNULIB_MBSCSPN@ +GNULIB_MBSINIT = @GNULIB_MBSINIT@ +GNULIB_MBSLEN = @GNULIB_MBSLEN@ +GNULIB_MBSNCASECMP = @GNULIB_MBSNCASECMP@ +GNULIB_MBSNLEN = @GNULIB_MBSNLEN@ +GNULIB_MBSNRTOWCS = @GNULIB_MBSNRTOWCS@ +GNULIB_MBSPBRK = @GNULIB_MBSPBRK@ +GNULIB_MBSPCASECMP = @GNULIB_MBSPCASECMP@ +GNULIB_MBSRCHR = @GNULIB_MBSRCHR@ +GNULIB_MBSRTOWCS = @GNULIB_MBSRTOWCS@ +GNULIB_MBSSEP = @GNULIB_MBSSEP@ +GNULIB_MBSSPN = @GNULIB_MBSSPN@ +GNULIB_MBSSTR = @GNULIB_MBSSTR@ +GNULIB_MBSTOK_R = @GNULIB_MBSTOK_R@ +GNULIB_MBTOWC = @GNULIB_MBTOWC@ +GNULIB_MEMCHR = @GNULIB_MEMCHR@ +GNULIB_MEMMEM = @GNULIB_MEMMEM@ +GNULIB_MEMPCPY = @GNULIB_MEMPCPY@ +GNULIB_MEMRCHR = @GNULIB_MEMRCHR@ +GNULIB_MKDIRAT = @GNULIB_MKDIRAT@ +GNULIB_MKDTEMP = @GNULIB_MKDTEMP@ +GNULIB_MKFIFO = @GNULIB_MKFIFO@ +GNULIB_MKFIFOAT = @GNULIB_MKFIFOAT@ +GNULIB_MKNOD = @GNULIB_MKNOD@ +GNULIB_MKNODAT = @GNULIB_MKNODAT@ +GNULIB_MKOSTEMP = @GNULIB_MKOSTEMP@ +GNULIB_MKOSTEMPS = @GNULIB_MKOSTEMPS@ +GNULIB_MKSTEMP = @GNULIB_MKSTEMP@ +GNULIB_MKSTEMPS = @GNULIB_MKSTEMPS@ +GNULIB_MKTIME = @GNULIB_MKTIME@ +GNULIB_NANOSLEEP = @GNULIB_NANOSLEEP@ +GNULIB_NL_LANGINFO = @GNULIB_NL_LANGINFO@ +GNULIB_OBSTACK_PRINTF = @GNULIB_OBSTACK_PRINTF@ +GNULIB_OBSTACK_PRINTF_POSIX = @GNULIB_OBSTACK_PRINTF_POSIX@ +GNULIB_OPEN = @GNULIB_OPEN@ +GNULIB_OPENAT = @GNULIB_OPENAT@ +GNULIB_PERROR = @GNULIB_PERROR@ +GNULIB_PIPE = @GNULIB_PIPE@ +GNULIB_PIPE2 = @GNULIB_PIPE2@ +GNULIB_POPEN = @GNULIB_POPEN@ +GNULIB_POSIX_SPAWN = @GNULIB_POSIX_SPAWN@ +GNULIB_POSIX_SPAWNATTR_DESTROY = @GNULIB_POSIX_SPAWNATTR_DESTROY@ +GNULIB_POSIX_SPAWNATTR_GETFLAGS = @GNULIB_POSIX_SPAWNATTR_GETFLAGS@ +GNULIB_POSIX_SPAWNATTR_GETPGROUP = @GNULIB_POSIX_SPAWNATTR_GETPGROUP@ +GNULIB_POSIX_SPAWNATTR_GETSCHEDPARAM = @GNULIB_POSIX_SPAWNATTR_GETSCHEDPARAM@ +GNULIB_POSIX_SPAWNATTR_GETSCHEDPOLICY = @GNULIB_POSIX_SPAWNATTR_GETSCHEDPOLICY@ +GNULIB_POSIX_SPAWNATTR_GETSIGDEFAULT = @GNULIB_POSIX_SPAWNATTR_GETSIGDEFAULT@ +GNULIB_POSIX_SPAWNATTR_GETSIGMASK = @GNULIB_POSIX_SPAWNATTR_GETSIGMASK@ +GNULIB_POSIX_SPAWNATTR_INIT = @GNULIB_POSIX_SPAWNATTR_INIT@ +GNULIB_POSIX_SPAWNATTR_SETFLAGS = @GNULIB_POSIX_SPAWNATTR_SETFLAGS@ +GNULIB_POSIX_SPAWNATTR_SETPGROUP = @GNULIB_POSIX_SPAWNATTR_SETPGROUP@ +GNULIB_POSIX_SPAWNATTR_SETSCHEDPARAM = @GNULIB_POSIX_SPAWNATTR_SETSCHEDPARAM@ +GNULIB_POSIX_SPAWNATTR_SETSCHEDPOLICY = @GNULIB_POSIX_SPAWNATTR_SETSCHEDPOLICY@ +GNULIB_POSIX_SPAWNATTR_SETSIGDEFAULT = @GNULIB_POSIX_SPAWNATTR_SETSIGDEFAULT@ +GNULIB_POSIX_SPAWNATTR_SETSIGMASK = @GNULIB_POSIX_SPAWNATTR_SETSIGMASK@ +GNULIB_POSIX_SPAWNP = @GNULIB_POSIX_SPAWNP@ +GNULIB_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSE = @GNULIB_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSE@ +GNULIB_POSIX_SPAWN_FILE_ACTIONS_ADDDUP2 = @GNULIB_POSIX_SPAWN_FILE_ACTIONS_ADDDUP2@ +GNULIB_POSIX_SPAWN_FILE_ACTIONS_ADDOPEN = @GNULIB_POSIX_SPAWN_FILE_ACTIONS_ADDOPEN@ +GNULIB_POSIX_SPAWN_FILE_ACTIONS_DESTROY = @GNULIB_POSIX_SPAWN_FILE_ACTIONS_DESTROY@ +GNULIB_POSIX_SPAWN_FILE_ACTIONS_INIT = @GNULIB_POSIX_SPAWN_FILE_ACTIONS_INIT@ +GNULIB_PREAD = @GNULIB_PREAD@ +GNULIB_PRINTF = @GNULIB_PRINTF@ +GNULIB_PRINTF_POSIX = @GNULIB_PRINTF_POSIX@ +GNULIB_PTSNAME = @GNULIB_PTSNAME@ +GNULIB_PUTC = @GNULIB_PUTC@ +GNULIB_PUTCHAR = @GNULIB_PUTCHAR@ +GNULIB_PUTENV = @GNULIB_PUTENV@ +GNULIB_PUTS = @GNULIB_PUTS@ +GNULIB_PWRITE = @GNULIB_PWRITE@ +GNULIB_RANDOM_R = @GNULIB_RANDOM_R@ +GNULIB_RAWMEMCHR = @GNULIB_RAWMEMCHR@ +GNULIB_READLINK = @GNULIB_READLINK@ +GNULIB_READLINKAT = @GNULIB_READLINKAT@ +GNULIB_REALLOC_POSIX = @GNULIB_REALLOC_POSIX@ +GNULIB_REALPATH = @GNULIB_REALPATH@ +GNULIB_REMOVE = @GNULIB_REMOVE@ +GNULIB_RENAME = @GNULIB_RENAME@ +GNULIB_RENAMEAT = @GNULIB_RENAMEAT@ +GNULIB_RMDIR = @GNULIB_RMDIR@ +GNULIB_ROUND = @GNULIB_ROUND@ +GNULIB_ROUNDF = @GNULIB_ROUNDF@ +GNULIB_ROUNDL = @GNULIB_ROUNDL@ +GNULIB_RPMATCH = @GNULIB_RPMATCH@ +GNULIB_SETENV = @GNULIB_SETENV@ +GNULIB_SETLOCALE = @GNULIB_SETLOCALE@ +GNULIB_SIGACTION = @GNULIB_SIGACTION@ +GNULIB_SIGNAL_H_SIGPIPE = @GNULIB_SIGNAL_H_SIGPIPE@ +GNULIB_SIGNBIT = @GNULIB_SIGNBIT@ +GNULIB_SIGPROCMASK = @GNULIB_SIGPROCMASK@ +GNULIB_SINL = @GNULIB_SINL@ +GNULIB_SLEEP = @GNULIB_SLEEP@ +GNULIB_SNPRINTF = @GNULIB_SNPRINTF@ +GNULIB_SPRINTF_POSIX = @GNULIB_SPRINTF_POSIX@ +GNULIB_SQRTL = @GNULIB_SQRTL@ +GNULIB_STAT = @GNULIB_STAT@ +GNULIB_STDIO_H_SIGPIPE = @GNULIB_STDIO_H_SIGPIPE@ +GNULIB_STPCPY = @GNULIB_STPCPY@ +GNULIB_STPNCPY = @GNULIB_STPNCPY@ +GNULIB_STRCASESTR = @GNULIB_STRCASESTR@ +GNULIB_STRCHRNUL = @GNULIB_STRCHRNUL@ +GNULIB_STRDUP = @GNULIB_STRDUP@ +GNULIB_STRERROR = @GNULIB_STRERROR@ +GNULIB_STRERROR_R = @GNULIB_STRERROR_R@ +GNULIB_STRNCAT = @GNULIB_STRNCAT@ +GNULIB_STRNDUP = @GNULIB_STRNDUP@ +GNULIB_STRNLEN = @GNULIB_STRNLEN@ +GNULIB_STRPBRK = @GNULIB_STRPBRK@ +GNULIB_STRPTIME = @GNULIB_STRPTIME@ +GNULIB_STRSEP = @GNULIB_STRSEP@ +GNULIB_STRSIGNAL = @GNULIB_STRSIGNAL@ +GNULIB_STRSTR = @GNULIB_STRSTR@ +GNULIB_STRTOD = @GNULIB_STRTOD@ +GNULIB_STRTOK_R = @GNULIB_STRTOK_R@ +GNULIB_STRTOLL = @GNULIB_STRTOLL@ +GNULIB_STRTOULL = @GNULIB_STRTOULL@ +GNULIB_STRVERSCMP = @GNULIB_STRVERSCMP@ +GNULIB_SYMLINK = @GNULIB_SYMLINK@ +GNULIB_SYMLINKAT = @GNULIB_SYMLINKAT@ +GNULIB_SYSTEM_POSIX = @GNULIB_SYSTEM_POSIX@ +GNULIB_TANL = @GNULIB_TANL@ +GNULIB_TIMEGM = @GNULIB_TIMEGM@ +GNULIB_TIME_R = @GNULIB_TIME_R@ +GNULIB_TMPFILE = @GNULIB_TMPFILE@ +GNULIB_TOWCTRANS = @GNULIB_TOWCTRANS@ +GNULIB_TRUNC = @GNULIB_TRUNC@ +GNULIB_TRUNCF = @GNULIB_TRUNCF@ +GNULIB_TRUNCL = @GNULIB_TRUNCL@ +GNULIB_TTYNAME_R = @GNULIB_TTYNAME_R@ +GNULIB_UNISTD_H_GETOPT = @GNULIB_UNISTD_H_GETOPT@ +GNULIB_UNISTD_H_SIGPIPE = @GNULIB_UNISTD_H_SIGPIPE@ +GNULIB_UNLINK = @GNULIB_UNLINK@ +GNULIB_UNLINKAT = @GNULIB_UNLINKAT@ +GNULIB_UNLOCKPT = @GNULIB_UNLOCKPT@ +GNULIB_UNSETENV = @GNULIB_UNSETENV@ +GNULIB_USLEEP = @GNULIB_USLEEP@ +GNULIB_UTIMENSAT = @GNULIB_UTIMENSAT@ +GNULIB_VASPRINTF = @GNULIB_VASPRINTF@ +GNULIB_VDPRINTF = @GNULIB_VDPRINTF@ +GNULIB_VFPRINTF = @GNULIB_VFPRINTF@ +GNULIB_VFPRINTF_POSIX = @GNULIB_VFPRINTF_POSIX@ +GNULIB_VPRINTF = @GNULIB_VPRINTF@ +GNULIB_VPRINTF_POSIX = @GNULIB_VPRINTF_POSIX@ +GNULIB_VSNPRINTF = @GNULIB_VSNPRINTF@ +GNULIB_VSPRINTF_POSIX = @GNULIB_VSPRINTF_POSIX@ +GNULIB_WAITPID = @GNULIB_WAITPID@ +GNULIB_WCPCPY = @GNULIB_WCPCPY@ +GNULIB_WCPNCPY = @GNULIB_WCPNCPY@ +GNULIB_WCRTOMB = @GNULIB_WCRTOMB@ +GNULIB_WCSCASECMP = @GNULIB_WCSCASECMP@ +GNULIB_WCSCAT = @GNULIB_WCSCAT@ +GNULIB_WCSCHR = @GNULIB_WCSCHR@ +GNULIB_WCSCMP = @GNULIB_WCSCMP@ +GNULIB_WCSCOLL = @GNULIB_WCSCOLL@ +GNULIB_WCSCPY = @GNULIB_WCSCPY@ +GNULIB_WCSCSPN = @GNULIB_WCSCSPN@ +GNULIB_WCSDUP = @GNULIB_WCSDUP@ +GNULIB_WCSLEN = @GNULIB_WCSLEN@ +GNULIB_WCSNCASECMP = @GNULIB_WCSNCASECMP@ +GNULIB_WCSNCAT = @GNULIB_WCSNCAT@ +GNULIB_WCSNCMP = @GNULIB_WCSNCMP@ +GNULIB_WCSNCPY = @GNULIB_WCSNCPY@ +GNULIB_WCSNLEN = @GNULIB_WCSNLEN@ +GNULIB_WCSNRTOMBS = @GNULIB_WCSNRTOMBS@ +GNULIB_WCSPBRK = @GNULIB_WCSPBRK@ +GNULIB_WCSRCHR = @GNULIB_WCSRCHR@ +GNULIB_WCSRTOMBS = @GNULIB_WCSRTOMBS@ +GNULIB_WCSSPN = @GNULIB_WCSSPN@ +GNULIB_WCSSTR = @GNULIB_WCSSTR@ +GNULIB_WCSTOK = @GNULIB_WCSTOK@ +GNULIB_WCSWIDTH = @GNULIB_WCSWIDTH@ +GNULIB_WCSXFRM = @GNULIB_WCSXFRM@ +GNULIB_WCTOB = @GNULIB_WCTOB@ +GNULIB_WCTOMB = @GNULIB_WCTOMB@ +GNULIB_WCTRANS = @GNULIB_WCTRANS@ +GNULIB_WCTYPE = @GNULIB_WCTYPE@ +GNULIB_WCWIDTH = @GNULIB_WCWIDTH@ +GNULIB_WMEMCHR = @GNULIB_WMEMCHR@ +GNULIB_WMEMCMP = @GNULIB_WMEMCMP@ +GNULIB_WMEMCPY = @GNULIB_WMEMCPY@ +GNULIB_WMEMMOVE = @GNULIB_WMEMMOVE@ +GNULIB_WMEMSET = @GNULIB_WMEMSET@ +GNULIB_WRITE = @GNULIB_WRITE@ +GNULIB__EXIT = @GNULIB__EXIT@ +GREP = @GREP@ +HAVE_ACOSL = @HAVE_ACOSL@ +HAVE_ASINL = @HAVE_ASINL@ +HAVE_ATANL = @HAVE_ATANL@ +HAVE_ATOLL = @HAVE_ATOLL@ +HAVE_BTOWC = @HAVE_BTOWC@ +HAVE_CANONICALIZE_FILE_NAME = @HAVE_CANONICALIZE_FILE_NAME@ +HAVE_CHOWN = @HAVE_CHOWN@ +HAVE_COSL = @HAVE_COSL@ +HAVE_DECL_ACOSL = @HAVE_DECL_ACOSL@ +HAVE_DECL_ASINL = @HAVE_DECL_ASINL@ +HAVE_DECL_ATANL = @HAVE_DECL_ATANL@ +HAVE_DECL_CEILF = @HAVE_DECL_CEILF@ +HAVE_DECL_CEILL = @HAVE_DECL_CEILL@ +HAVE_DECL_COSL = @HAVE_DECL_COSL@ +HAVE_DECL_ENVIRON = @HAVE_DECL_ENVIRON@ +HAVE_DECL_EXPL = @HAVE_DECL_EXPL@ +HAVE_DECL_FCHDIR = @HAVE_DECL_FCHDIR@ +HAVE_DECL_FLOORF = @HAVE_DECL_FLOORF@ +HAVE_DECL_FLOORL = @HAVE_DECL_FLOORL@ +HAVE_DECL_FPURGE = @HAVE_DECL_FPURGE@ +HAVE_DECL_FREXPL = @HAVE_DECL_FREXPL@ +HAVE_DECL_FSEEKO = @HAVE_DECL_FSEEKO@ +HAVE_DECL_FTELLO = @HAVE_DECL_FTELLO@ +HAVE_DECL_GETDELIM = @HAVE_DECL_GETDELIM@ +HAVE_DECL_GETDOMAINNAME = @HAVE_DECL_GETDOMAINNAME@ +HAVE_DECL_GETLINE = @HAVE_DECL_GETLINE@ +HAVE_DECL_GETLOADAVG = @HAVE_DECL_GETLOADAVG@ +HAVE_DECL_GETLOGIN_R = @HAVE_DECL_GETLOGIN_R@ +HAVE_DECL_GETPAGESIZE = @HAVE_DECL_GETPAGESIZE@ +HAVE_DECL_GETUSERSHELL = @HAVE_DECL_GETUSERSHELL@ +HAVE_DECL_LDEXPL = @HAVE_DECL_LDEXPL@ +HAVE_DECL_LOCALTIME_R = @HAVE_DECL_LOCALTIME_R@ +HAVE_DECL_LOGB = @HAVE_DECL_LOGB@ +HAVE_DECL_LOGL = @HAVE_DECL_LOGL@ +HAVE_DECL_MEMMEM = @HAVE_DECL_MEMMEM@ +HAVE_DECL_MEMRCHR = @HAVE_DECL_MEMRCHR@ +HAVE_DECL_OBSTACK_PRINTF = @HAVE_DECL_OBSTACK_PRINTF@ +HAVE_DECL_ROUND = @HAVE_DECL_ROUND@ +HAVE_DECL_ROUNDF = @HAVE_DECL_ROUNDF@ +HAVE_DECL_ROUNDL = @HAVE_DECL_ROUNDL@ +HAVE_DECL_SETENV = @HAVE_DECL_SETENV@ +HAVE_DECL_SINL = @HAVE_DECL_SINL@ +HAVE_DECL_SNPRINTF = @HAVE_DECL_SNPRINTF@ +HAVE_DECL_SQRTL = @HAVE_DECL_SQRTL@ +HAVE_DECL_STRDUP = @HAVE_DECL_STRDUP@ +HAVE_DECL_STRERROR_R = @HAVE_DECL_STRERROR_R@ +HAVE_DECL_STRNDUP = @HAVE_DECL_STRNDUP@ +HAVE_DECL_STRNLEN = @HAVE_DECL_STRNLEN@ +HAVE_DECL_STRSIGNAL = @HAVE_DECL_STRSIGNAL@ +HAVE_DECL_STRTOK_R = @HAVE_DECL_STRTOK_R@ +HAVE_DECL_TANL = @HAVE_DECL_TANL@ +HAVE_DECL_TRUNC = @HAVE_DECL_TRUNC@ +HAVE_DECL_TRUNCF = @HAVE_DECL_TRUNCF@ +HAVE_DECL_TRUNCL = @HAVE_DECL_TRUNCL@ +HAVE_DECL_TTYNAME_R = @HAVE_DECL_TTYNAME_R@ +HAVE_DECL_UNSETENV = @HAVE_DECL_UNSETENV@ +HAVE_DECL_VSNPRINTF = @HAVE_DECL_VSNPRINTF@ +HAVE_DECL_WCTOB = @HAVE_DECL_WCTOB@ +HAVE_DECL_WCWIDTH = @HAVE_DECL_WCWIDTH@ +HAVE_DPRINTF = @HAVE_DPRINTF@ +HAVE_DUP2 = @HAVE_DUP2@ +HAVE_DUP3 = @HAVE_DUP3@ +HAVE_DUPLOCALE = @HAVE_DUPLOCALE@ +HAVE_EUIDACCESS = @HAVE_EUIDACCESS@ +HAVE_EXPL = @HAVE_EXPL@ +HAVE_FACCESSAT = @HAVE_FACCESSAT@ +HAVE_FCHDIR = @HAVE_FCHDIR@ +HAVE_FCHMODAT = @HAVE_FCHMODAT@ +HAVE_FCHOWNAT = @HAVE_FCHOWNAT@ +HAVE_FCNTL = @HAVE_FCNTL@ +HAVE_FEATURES_H = @HAVE_FEATURES_H@ +HAVE_FSEEKO = @HAVE_FSEEKO@ +HAVE_FSTATAT = @HAVE_FSTATAT@ +HAVE_FSYNC = @HAVE_FSYNC@ +HAVE_FTELLO = @HAVE_FTELLO@ +HAVE_FTRUNCATE = @HAVE_FTRUNCATE@ +HAVE_FUTIMENS = @HAVE_FUTIMENS@ +HAVE_GETDTABLESIZE = @HAVE_GETDTABLESIZE@ +HAVE_GETGROUPS = @HAVE_GETGROUPS@ +HAVE_GETHOSTNAME = @HAVE_GETHOSTNAME@ +HAVE_GETLOGIN = @HAVE_GETLOGIN@ +HAVE_GETOPT_H = @HAVE_GETOPT_H@ +HAVE_GETPAGESIZE = @HAVE_GETPAGESIZE@ +HAVE_GETSUBOPT = @HAVE_GETSUBOPT@ +HAVE_GETTIMEOFDAY = @HAVE_GETTIMEOFDAY@ +HAVE_GRANTPT = @HAVE_GRANTPT@ +HAVE_INTTYPES_H = @HAVE_INTTYPES_H@ +HAVE_ISNAND = @HAVE_ISNAND@ +HAVE_ISNANF = @HAVE_ISNANF@ +HAVE_ISNANL = @HAVE_ISNANL@ +HAVE_ISWBLANK = @HAVE_ISWBLANK@ +HAVE_ISWCNTRL = @HAVE_ISWCNTRL@ +HAVE_LANGINFO_CODESET = @HAVE_LANGINFO_CODESET@ +HAVE_LANGINFO_ERA = @HAVE_LANGINFO_ERA@ +HAVE_LANGINFO_H = @HAVE_LANGINFO_H@ +HAVE_LANGINFO_T_FMT_AMPM = @HAVE_LANGINFO_T_FMT_AMPM@ +HAVE_LANGINFO_YESEXPR = @HAVE_LANGINFO_YESEXPR@ +HAVE_LCHMOD = @HAVE_LCHMOD@ +HAVE_LCHOWN = @HAVE_LCHOWN@ +HAVE_LIBSIGSEGV = @HAVE_LIBSIGSEGV@ +HAVE_LINK = @HAVE_LINK@ +HAVE_LINKAT = @HAVE_LINKAT@ +HAVE_LOGL = @HAVE_LOGL@ +HAVE_LONG_LONG_INT = @HAVE_LONG_LONG_INT@ +HAVE_LSTAT = @HAVE_LSTAT@ +HAVE_MBRLEN = @HAVE_MBRLEN@ +HAVE_MBRTOWC = @HAVE_MBRTOWC@ +HAVE_MBSINIT = @HAVE_MBSINIT@ +HAVE_MBSLEN = @HAVE_MBSLEN@ +HAVE_MBSNRTOWCS = @HAVE_MBSNRTOWCS@ +HAVE_MBSRTOWCS = @HAVE_MBSRTOWCS@ +HAVE_MEMCHR = @HAVE_MEMCHR@ +HAVE_MEMPCPY = @HAVE_MEMPCPY@ +HAVE_MKDIRAT = @HAVE_MKDIRAT@ +HAVE_MKDTEMP = @HAVE_MKDTEMP@ +HAVE_MKFIFO = @HAVE_MKFIFO@ +HAVE_MKFIFOAT = @HAVE_MKFIFOAT@ +HAVE_MKNOD = @HAVE_MKNOD@ +HAVE_MKNODAT = @HAVE_MKNODAT@ +HAVE_MKOSTEMP = @HAVE_MKOSTEMP@ +HAVE_MKOSTEMPS = @HAVE_MKOSTEMPS@ +HAVE_MKSTEMP = @HAVE_MKSTEMP@ +HAVE_MKSTEMPS = @HAVE_MKSTEMPS@ +HAVE_NANOSLEEP = @HAVE_NANOSLEEP@ +HAVE_NL_LANGINFO = @HAVE_NL_LANGINFO@ +HAVE_OPENAT = @HAVE_OPENAT@ +HAVE_OS_H = @HAVE_OS_H@ +HAVE_PIPE = @HAVE_PIPE@ +HAVE_PIPE2 = @HAVE_PIPE2@ +HAVE_POSIX_SIGNALBLOCKING = @HAVE_POSIX_SIGNALBLOCKING@ +HAVE_POSIX_SPAWN = @HAVE_POSIX_SPAWN@ +HAVE_POSIX_SPAWNATTR_T = @HAVE_POSIX_SPAWNATTR_T@ +HAVE_POSIX_SPAWN_FILE_ACTIONS_T = @HAVE_POSIX_SPAWN_FILE_ACTIONS_T@ +HAVE_PREAD = @HAVE_PREAD@ +HAVE_PTSNAME = @HAVE_PTSNAME@ +HAVE_PWRITE = @HAVE_PWRITE@ +HAVE_RANDOM_H = @HAVE_RANDOM_H@ +HAVE_RANDOM_R = @HAVE_RANDOM_R@ +HAVE_RAWMEMCHR = @HAVE_RAWMEMCHR@ +HAVE_READLINK = @HAVE_READLINK@ +HAVE_READLINKAT = @HAVE_READLINKAT@ +HAVE_REALPATH = @HAVE_REALPATH@ +HAVE_RENAMEAT = @HAVE_RENAMEAT@ +HAVE_RPMATCH = @HAVE_RPMATCH@ +HAVE_SCHED_H = @HAVE_SCHED_H@ +HAVE_SETENV = @HAVE_SETENV@ +HAVE_SIGACTION = @HAVE_SIGACTION@ +HAVE_SIGINFO_T = @HAVE_SIGINFO_T@ +HAVE_SIGNED_SIG_ATOMIC_T = @HAVE_SIGNED_SIG_ATOMIC_T@ +HAVE_SIGNED_WCHAR_T = @HAVE_SIGNED_WCHAR_T@ +HAVE_SIGNED_WINT_T = @HAVE_SIGNED_WINT_T@ +HAVE_SIGSET_T = @HAVE_SIGSET_T@ +HAVE_SINL = @HAVE_SINL@ +HAVE_SLEEP = @HAVE_SLEEP@ +HAVE_SPAWN_H = @HAVE_SPAWN_H@ +HAVE_SQRTL = @HAVE_SQRTL@ +HAVE_STDINT_H = @HAVE_STDINT_H@ +HAVE_STPCPY = @HAVE_STPCPY@ +HAVE_STPNCPY = @HAVE_STPNCPY@ +HAVE_STRCASESTR = @HAVE_STRCASESTR@ +HAVE_STRCHRNUL = @HAVE_STRCHRNUL@ +HAVE_STRPBRK = @HAVE_STRPBRK@ +HAVE_STRPTIME = @HAVE_STRPTIME@ +HAVE_STRSEP = @HAVE_STRSEP@ +HAVE_STRTOD = @HAVE_STRTOD@ +HAVE_STRTOLL = @HAVE_STRTOLL@ +HAVE_STRTOULL = @HAVE_STRTOULL@ +HAVE_STRUCT_RANDOM_DATA = @HAVE_STRUCT_RANDOM_DATA@ +HAVE_STRUCT_SCHED_PARAM = @HAVE_STRUCT_SCHED_PARAM@ +HAVE_STRUCT_SIGACTION_SA_SIGACTION = @HAVE_STRUCT_SIGACTION_SA_SIGACTION@ +HAVE_STRUCT_TIMEVAL = @HAVE_STRUCT_TIMEVAL@ +HAVE_STRVERSCMP = @HAVE_STRVERSCMP@ +HAVE_SYMLINK = @HAVE_SYMLINK@ +HAVE_SYMLINKAT = @HAVE_SYMLINKAT@ +HAVE_SYS_BITYPES_H = @HAVE_SYS_BITYPES_H@ +HAVE_SYS_INTTYPES_H = @HAVE_SYS_INTTYPES_H@ +HAVE_SYS_LOADAVG_H = @HAVE_SYS_LOADAVG_H@ +HAVE_SYS_PARAM_H = @HAVE_SYS_PARAM_H@ +HAVE_SYS_TIME_H = @HAVE_SYS_TIME_H@ +HAVE_SYS_TYPES_H = @HAVE_SYS_TYPES_H@ +HAVE_TANL = @HAVE_TANL@ +HAVE_TIMEGM = @HAVE_TIMEGM@ +HAVE_TYPE_VOLATILE_SIG_ATOMIC_T = @HAVE_TYPE_VOLATILE_SIG_ATOMIC_T@ +HAVE_UNISTD_H = @HAVE_UNISTD_H@ +HAVE_UNLINKAT = @HAVE_UNLINKAT@ +HAVE_UNLOCKPT = @HAVE_UNLOCKPT@ +HAVE_UNSIGNED_LONG_LONG_INT = @HAVE_UNSIGNED_LONG_LONG_INT@ +HAVE_USLEEP = @HAVE_USLEEP@ +HAVE_UTIMENSAT = @HAVE_UTIMENSAT@ +HAVE_VASPRINTF = @HAVE_VASPRINTF@ +HAVE_VDPRINTF = @HAVE_VDPRINTF@ +HAVE_WCHAR_H = @HAVE_WCHAR_H@ +HAVE_WCHAR_T = @HAVE_WCHAR_T@ +HAVE_WCPCPY = @HAVE_WCPCPY@ +HAVE_WCPNCPY = @HAVE_WCPNCPY@ +HAVE_WCRTOMB = @HAVE_WCRTOMB@ +HAVE_WCSCASECMP = @HAVE_WCSCASECMP@ +HAVE_WCSCAT = @HAVE_WCSCAT@ +HAVE_WCSCHR = @HAVE_WCSCHR@ +HAVE_WCSCMP = @HAVE_WCSCMP@ +HAVE_WCSCOLL = @HAVE_WCSCOLL@ +HAVE_WCSCPY = @HAVE_WCSCPY@ +HAVE_WCSCSPN = @HAVE_WCSCSPN@ +HAVE_WCSDUP = @HAVE_WCSDUP@ +HAVE_WCSLEN = @HAVE_WCSLEN@ +HAVE_WCSNCASECMP = @HAVE_WCSNCASECMP@ +HAVE_WCSNCAT = @HAVE_WCSNCAT@ +HAVE_WCSNCMP = @HAVE_WCSNCMP@ +HAVE_WCSNCPY = @HAVE_WCSNCPY@ +HAVE_WCSNLEN = @HAVE_WCSNLEN@ +HAVE_WCSNRTOMBS = @HAVE_WCSNRTOMBS@ +HAVE_WCSPBRK = @HAVE_WCSPBRK@ +HAVE_WCSRCHR = @HAVE_WCSRCHR@ +HAVE_WCSRTOMBS = @HAVE_WCSRTOMBS@ +HAVE_WCSSPN = @HAVE_WCSSPN@ +HAVE_WCSSTR = @HAVE_WCSSTR@ +HAVE_WCSTOK = @HAVE_WCSTOK@ +HAVE_WCSWIDTH = @HAVE_WCSWIDTH@ +HAVE_WCSXFRM = @HAVE_WCSXFRM@ +HAVE_WCTRANS_T = @HAVE_WCTRANS_T@ +HAVE_WCTYPE_H = @HAVE_WCTYPE_H@ +HAVE_WCTYPE_T = @HAVE_WCTYPE_T@ +HAVE_WINT_T = @HAVE_WINT_T@ +HAVE_WMEMCHR = @HAVE_WMEMCHR@ +HAVE_WMEMCMP = @HAVE_WMEMCMP@ +HAVE_WMEMCPY = @HAVE_WMEMCPY@ +HAVE_WMEMMOVE = @HAVE_WMEMMOVE@ +HAVE_WMEMSET = @HAVE_WMEMSET@ +HAVE_XLOCALE_H = @HAVE_XLOCALE_H@ +HAVE__BOOL = @HAVE__BOOL@ +HAVE__EXIT = @HAVE__EXIT@ +INCLUDE_NEXT = @INCLUDE_NEXT@ +INCLUDE_NEXT_AS_FIRST_DIRECTIVE = @INCLUDE_NEXT_AS_FIRST_DIRECTIVE@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +LDFLAGS = @LDFLAGS@ +LIBCSTACK = @LIBCSTACK@ +LIBINTL = @LIBINTL@ +LIBM4_LIBDEPS = @LIBM4_LIBDEPS@ +LIBM4_LTLIBDEPS = @LIBM4_LTLIBDEPS@ +LIBMULTITHREAD = @LIBMULTITHREAD@ +LIBOBJS = @LIBOBJS@ +LIBPTH = @LIBPTH@ +LIBPTH_PREFIX = @LIBPTH_PREFIX@ +LIBS = @LIBS@ +LIBSIGSEGV = @LIBSIGSEGV@ +LIBSIGSEGV_PREFIX = @LIBSIGSEGV_PREFIX@ +LIBTESTS_LIBDEPS = @LIBTESTS_LIBDEPS@ +LIBTHREAD = @LIBTHREAD@ +LOCALCHARSET_TESTS_ENVIRONMENT = @LOCALCHARSET_TESTS_ENVIRONMENT@ +LOCALE_FR = @LOCALE_FR@ +LOCALE_FR_UTF8 = @LOCALE_FR_UTF8@ +LOCALE_JA = @LOCALE_JA@ +LOCALE_TR_UTF8 = @LOCALE_TR_UTF8@ +LOCALE_ZH_CN = @LOCALE_ZH_CN@ +LTLIBCSTACK = @LTLIBCSTACK@ +LTLIBINTL = @LTLIBINTL@ +LTLIBMULTITHREAD = @LTLIBMULTITHREAD@ +LTLIBOBJS = @LTLIBOBJS@ +LTLIBPTH = @LTLIBPTH@ +LTLIBSIGSEGV = @LTLIBSIGSEGV@ +LTLIBTHREAD = @LTLIBTHREAD@ +M4_LIBOBJS = @M4_LIBOBJS@ +M4_LTLIBOBJS = @M4_LTLIBOBJS@ +M4tests_LIBOBJS = @M4tests_LIBOBJS@ +M4tests_LTLIBOBJS = @M4tests_LTLIBOBJS@ +M4tests_WITNESS = @M4tests_WITNESS@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +NEXT_AS_FIRST_DIRECTIVE_ERRNO_H = @NEXT_AS_FIRST_DIRECTIVE_ERRNO_H@ +NEXT_AS_FIRST_DIRECTIVE_FCNTL_H = @NEXT_AS_FIRST_DIRECTIVE_FCNTL_H@ +NEXT_AS_FIRST_DIRECTIVE_FLOAT_H = @NEXT_AS_FIRST_DIRECTIVE_FLOAT_H@ +NEXT_AS_FIRST_DIRECTIVE_GETOPT_H = @NEXT_AS_FIRST_DIRECTIVE_GETOPT_H@ +NEXT_AS_FIRST_DIRECTIVE_LANGINFO_H = @NEXT_AS_FIRST_DIRECTIVE_LANGINFO_H@ +NEXT_AS_FIRST_DIRECTIVE_LOCALE_H = @NEXT_AS_FIRST_DIRECTIVE_LOCALE_H@ +NEXT_AS_FIRST_DIRECTIVE_MATH_H = @NEXT_AS_FIRST_DIRECTIVE_MATH_H@ +NEXT_AS_FIRST_DIRECTIVE_SCHED_H = @NEXT_AS_FIRST_DIRECTIVE_SCHED_H@ +NEXT_AS_FIRST_DIRECTIVE_SIGNAL_H = @NEXT_AS_FIRST_DIRECTIVE_SIGNAL_H@ +NEXT_AS_FIRST_DIRECTIVE_SPAWN_H = @NEXT_AS_FIRST_DIRECTIVE_SPAWN_H@ +NEXT_AS_FIRST_DIRECTIVE_STDARG_H = @NEXT_AS_FIRST_DIRECTIVE_STDARG_H@ +NEXT_AS_FIRST_DIRECTIVE_STDDEF_H = @NEXT_AS_FIRST_DIRECTIVE_STDDEF_H@ +NEXT_AS_FIRST_DIRECTIVE_STDINT_H = @NEXT_AS_FIRST_DIRECTIVE_STDINT_H@ +NEXT_AS_FIRST_DIRECTIVE_STDIO_H = @NEXT_AS_FIRST_DIRECTIVE_STDIO_H@ +NEXT_AS_FIRST_DIRECTIVE_STDLIB_H = @NEXT_AS_FIRST_DIRECTIVE_STDLIB_H@ +NEXT_AS_FIRST_DIRECTIVE_STRING_H = @NEXT_AS_FIRST_DIRECTIVE_STRING_H@ +NEXT_AS_FIRST_DIRECTIVE_SYS_STAT_H = @NEXT_AS_FIRST_DIRECTIVE_SYS_STAT_H@ +NEXT_AS_FIRST_DIRECTIVE_SYS_TIME_H = @NEXT_AS_FIRST_DIRECTIVE_SYS_TIME_H@ +NEXT_AS_FIRST_DIRECTIVE_SYS_WAIT_H = @NEXT_AS_FIRST_DIRECTIVE_SYS_WAIT_H@ +NEXT_AS_FIRST_DIRECTIVE_TIME_H = @NEXT_AS_FIRST_DIRECTIVE_TIME_H@ +NEXT_AS_FIRST_DIRECTIVE_UNISTD_H = @NEXT_AS_FIRST_DIRECTIVE_UNISTD_H@ +NEXT_AS_FIRST_DIRECTIVE_WCHAR_H = @NEXT_AS_FIRST_DIRECTIVE_WCHAR_H@ +NEXT_AS_FIRST_DIRECTIVE_WCTYPE_H = @NEXT_AS_FIRST_DIRECTIVE_WCTYPE_H@ +NEXT_ERRNO_H = @NEXT_ERRNO_H@ +NEXT_FCNTL_H = @NEXT_FCNTL_H@ +NEXT_FLOAT_H = @NEXT_FLOAT_H@ +NEXT_GETOPT_H = @NEXT_GETOPT_H@ +NEXT_LANGINFO_H = @NEXT_LANGINFO_H@ +NEXT_LOCALE_H = @NEXT_LOCALE_H@ +NEXT_MATH_H = @NEXT_MATH_H@ +NEXT_SCHED_H = @NEXT_SCHED_H@ +NEXT_SIGNAL_H = @NEXT_SIGNAL_H@ +NEXT_SPAWN_H = @NEXT_SPAWN_H@ +NEXT_STDARG_H = @NEXT_STDARG_H@ +NEXT_STDDEF_H = @NEXT_STDDEF_H@ +NEXT_STDINT_H = @NEXT_STDINT_H@ +NEXT_STDIO_H = @NEXT_STDIO_H@ +NEXT_STDLIB_H = @NEXT_STDLIB_H@ +NEXT_STRING_H = @NEXT_STRING_H@ +NEXT_SYS_STAT_H = @NEXT_SYS_STAT_H@ +NEXT_SYS_TIME_H = @NEXT_SYS_TIME_H@ +NEXT_SYS_WAIT_H = @NEXT_SYS_WAIT_H@ +NEXT_TIME_H = @NEXT_TIME_H@ +NEXT_UNISTD_H = @NEXT_UNISTD_H@ +NEXT_WCHAR_H = @NEXT_WCHAR_H@ +NEXT_WCTYPE_H = @NEXT_WCTYPE_H@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PRAGMA_COLUMNS = @PRAGMA_COLUMNS@ +PRAGMA_SYSTEM_HEADER = @PRAGMA_SYSTEM_HEADER@ +PTHREAD_H_DEFINES_STRUCT_TIMESPEC = @PTHREAD_H_DEFINES_STRUCT_TIMESPEC@ +PTRDIFF_T_SUFFIX = @PTRDIFF_T_SUFFIX@ +RANLIB = @RANLIB@ +REPLACE_BTOWC = @REPLACE_BTOWC@ +REPLACE_CALLOC = @REPLACE_CALLOC@ +REPLACE_CANONICALIZE_FILE_NAME = @REPLACE_CANONICALIZE_FILE_NAME@ +REPLACE_CEIL = @REPLACE_CEIL@ +REPLACE_CEILF = @REPLACE_CEILF@ +REPLACE_CEILL = @REPLACE_CEILL@ +REPLACE_CHOWN = @REPLACE_CHOWN@ +REPLACE_CLOSE = @REPLACE_CLOSE@ +REPLACE_DPRINTF = @REPLACE_DPRINTF@ +REPLACE_DUP = @REPLACE_DUP@ +REPLACE_DUP2 = @REPLACE_DUP2@ +REPLACE_DUPLOCALE = @REPLACE_DUPLOCALE@ +REPLACE_FCHOWNAT = @REPLACE_FCHOWNAT@ +REPLACE_FCLOSE = @REPLACE_FCLOSE@ +REPLACE_FCNTL = @REPLACE_FCNTL@ +REPLACE_FFLUSH = @REPLACE_FFLUSH@ +REPLACE_FLOOR = @REPLACE_FLOOR@ +REPLACE_FLOORF = @REPLACE_FLOORF@ +REPLACE_FLOORL = @REPLACE_FLOORL@ +REPLACE_FOPEN = @REPLACE_FOPEN@ +REPLACE_FPRINTF = @REPLACE_FPRINTF@ +REPLACE_FPURGE = @REPLACE_FPURGE@ +REPLACE_FREOPEN = @REPLACE_FREOPEN@ +REPLACE_FREXP = @REPLACE_FREXP@ +REPLACE_FREXPL = @REPLACE_FREXPL@ +REPLACE_FSEEK = @REPLACE_FSEEK@ +REPLACE_FSEEKO = @REPLACE_FSEEKO@ +REPLACE_FSTAT = @REPLACE_FSTAT@ +REPLACE_FSTATAT = @REPLACE_FSTATAT@ +REPLACE_FTELL = @REPLACE_FTELL@ +REPLACE_FTELLO = @REPLACE_FTELLO@ +REPLACE_FUTIMENS = @REPLACE_FUTIMENS@ +REPLACE_GETCWD = @REPLACE_GETCWD@ +REPLACE_GETDELIM = @REPLACE_GETDELIM@ +REPLACE_GETDOMAINNAME = @REPLACE_GETDOMAINNAME@ +REPLACE_GETGROUPS = @REPLACE_GETGROUPS@ +REPLACE_GETLINE = @REPLACE_GETLINE@ +REPLACE_GETLOGIN_R = @REPLACE_GETLOGIN_R@ +REPLACE_GETPAGESIZE = @REPLACE_GETPAGESIZE@ +REPLACE_GETTIMEOFDAY = @REPLACE_GETTIMEOFDAY@ +REPLACE_HUGE_VAL = @REPLACE_HUGE_VAL@ +REPLACE_ISFINITE = @REPLACE_ISFINITE@ +REPLACE_ISINF = @REPLACE_ISINF@ +REPLACE_ISNAN = @REPLACE_ISNAN@ +REPLACE_ISWBLANK = @REPLACE_ISWBLANK@ +REPLACE_ISWCNTRL = @REPLACE_ISWCNTRL@ +REPLACE_LCHOWN = @REPLACE_LCHOWN@ +REPLACE_LDEXPL = @REPLACE_LDEXPL@ +REPLACE_LINK = @REPLACE_LINK@ +REPLACE_LINKAT = @REPLACE_LINKAT@ +REPLACE_LOCALTIME_R = @REPLACE_LOCALTIME_R@ +REPLACE_LSEEK = @REPLACE_LSEEK@ +REPLACE_LSTAT = @REPLACE_LSTAT@ +REPLACE_MALLOC = @REPLACE_MALLOC@ +REPLACE_MBRLEN = @REPLACE_MBRLEN@ +REPLACE_MBRTOWC = @REPLACE_MBRTOWC@ +REPLACE_MBSINIT = @REPLACE_MBSINIT@ +REPLACE_MBSNRTOWCS = @REPLACE_MBSNRTOWCS@ +REPLACE_MBSRTOWCS = @REPLACE_MBSRTOWCS@ +REPLACE_MBSTATE_T = @REPLACE_MBSTATE_T@ +REPLACE_MBTOWC = @REPLACE_MBTOWC@ +REPLACE_MEMCHR = @REPLACE_MEMCHR@ +REPLACE_MEMMEM = @REPLACE_MEMMEM@ +REPLACE_MKDIR = @REPLACE_MKDIR@ +REPLACE_MKFIFO = @REPLACE_MKFIFO@ +REPLACE_MKNOD = @REPLACE_MKNOD@ +REPLACE_MKSTEMP = @REPLACE_MKSTEMP@ +REPLACE_MKTIME = @REPLACE_MKTIME@ +REPLACE_NAN = @REPLACE_NAN@ +REPLACE_NANOSLEEP = @REPLACE_NANOSLEEP@ +REPLACE_NL_LANGINFO = @REPLACE_NL_LANGINFO@ +REPLACE_NULL = @REPLACE_NULL@ +REPLACE_OBSTACK_PRINTF = @REPLACE_OBSTACK_PRINTF@ +REPLACE_OPEN = @REPLACE_OPEN@ +REPLACE_OPENAT = @REPLACE_OPENAT@ +REPLACE_PERROR = @REPLACE_PERROR@ +REPLACE_POPEN = @REPLACE_POPEN@ +REPLACE_POSIX_SPAWN = @REPLACE_POSIX_SPAWN@ +REPLACE_PREAD = @REPLACE_PREAD@ +REPLACE_PRINTF = @REPLACE_PRINTF@ +REPLACE_PUTENV = @REPLACE_PUTENV@ +REPLACE_PWRITE = @REPLACE_PWRITE@ +REPLACE_READLINK = @REPLACE_READLINK@ +REPLACE_REALLOC = @REPLACE_REALLOC@ +REPLACE_REALPATH = @REPLACE_REALPATH@ +REPLACE_REMOVE = @REPLACE_REMOVE@ +REPLACE_RENAME = @REPLACE_RENAME@ +REPLACE_RENAMEAT = @REPLACE_RENAMEAT@ +REPLACE_RMDIR = @REPLACE_RMDIR@ +REPLACE_ROUND = @REPLACE_ROUND@ +REPLACE_ROUNDF = @REPLACE_ROUNDF@ +REPLACE_ROUNDL = @REPLACE_ROUNDL@ +REPLACE_SETENV = @REPLACE_SETENV@ +REPLACE_SETLOCALE = @REPLACE_SETLOCALE@ +REPLACE_SIGNBIT = @REPLACE_SIGNBIT@ +REPLACE_SIGNBIT_USING_GCC = @REPLACE_SIGNBIT_USING_GCC@ +REPLACE_SLEEP = @REPLACE_SLEEP@ +REPLACE_SNPRINTF = @REPLACE_SNPRINTF@ +REPLACE_SPRINTF = @REPLACE_SPRINTF@ +REPLACE_STAT = @REPLACE_STAT@ +REPLACE_STDIO_WRITE_FUNCS = @REPLACE_STDIO_WRITE_FUNCS@ +REPLACE_STPNCPY = @REPLACE_STPNCPY@ +REPLACE_STRCASESTR = @REPLACE_STRCASESTR@ +REPLACE_STRDUP = @REPLACE_STRDUP@ +REPLACE_STRERROR = @REPLACE_STRERROR@ +REPLACE_STRERROR_R = @REPLACE_STRERROR_R@ +REPLACE_STRNCAT = @REPLACE_STRNCAT@ +REPLACE_STRNDUP = @REPLACE_STRNDUP@ +REPLACE_STRNLEN = @REPLACE_STRNLEN@ +REPLACE_STRSIGNAL = @REPLACE_STRSIGNAL@ +REPLACE_STRSTR = @REPLACE_STRSTR@ +REPLACE_STRTOD = @REPLACE_STRTOD@ +REPLACE_STRTOK_R = @REPLACE_STRTOK_R@ +REPLACE_SYMLINK = @REPLACE_SYMLINK@ +REPLACE_TIMEGM = @REPLACE_TIMEGM@ +REPLACE_TMPFILE = @REPLACE_TMPFILE@ +REPLACE_TRUNC = @REPLACE_TRUNC@ +REPLACE_TRUNCF = @REPLACE_TRUNCF@ +REPLACE_TRUNCL = @REPLACE_TRUNCL@ +REPLACE_TTYNAME_R = @REPLACE_TTYNAME_R@ +REPLACE_UNLINK = @REPLACE_UNLINK@ +REPLACE_UNLINKAT = @REPLACE_UNLINKAT@ +REPLACE_UNSETENV = @REPLACE_UNSETENV@ +REPLACE_USLEEP = @REPLACE_USLEEP@ +REPLACE_UTIMENSAT = @REPLACE_UTIMENSAT@ +REPLACE_VASPRINTF = @REPLACE_VASPRINTF@ +REPLACE_VDPRINTF = @REPLACE_VDPRINTF@ +REPLACE_VFPRINTF = @REPLACE_VFPRINTF@ +REPLACE_VPRINTF = @REPLACE_VPRINTF@ +REPLACE_VSNPRINTF = @REPLACE_VSNPRINTF@ +REPLACE_VSPRINTF = @REPLACE_VSPRINTF@ +REPLACE_WCRTOMB = @REPLACE_WCRTOMB@ +REPLACE_WCSNRTOMBS = @REPLACE_WCSNRTOMBS@ +REPLACE_WCSRTOMBS = @REPLACE_WCSRTOMBS@ +REPLACE_WCSWIDTH = @REPLACE_WCSWIDTH@ +REPLACE_WCTOB = @REPLACE_WCTOB@ +REPLACE_WCTOMB = @REPLACE_WCTOMB@ +REPLACE_WCWIDTH = @REPLACE_WCWIDTH@ +REPLACE_WRITE = @REPLACE_WRITE@ +SCHED_H = @SCHED_H@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SIG_ATOMIC_T_SUFFIX = @SIG_ATOMIC_T_SUFFIX@ +SIZE_T_SUFFIX = @SIZE_T_SUFFIX@ +STDARG_H = @STDARG_H@ +STDBOOL_H = @STDBOOL_H@ +STDDEF_H = @STDDEF_H@ +STDINT_H = @STDINT_H@ +STRIP = @STRIP@ +SYS_TIME_H_DEFINES_STRUCT_TIMESPEC = @SYS_TIME_H_DEFINES_STRUCT_TIMESPEC@ +TIME_H_DEFINES_STRUCT_TIMESPEC = @TIME_H_DEFINES_STRUCT_TIMESPEC@ +UNDEFINE_STRTOK_R = @UNDEFINE_STRTOK_R@ +UNISTD_H_HAVE_WINSOCK2_H = @UNISTD_H_HAVE_WINSOCK2_H@ +UNISTD_H_HAVE_WINSOCK2_H_AND_USE_SOCKETS = @UNISTD_H_HAVE_WINSOCK2_H_AND_USE_SOCKETS@ +VERSION = @VERSION@ +WARN_CFLAGS = @WARN_CFLAGS@ +WCHAR_T_SUFFIX = @WCHAR_T_SUFFIX@ +WERROR_CFLAGS = @WERROR_CFLAGS@ +WINT_T_SUFFIX = @WINT_T_SUFFIX@ +abs_aux_dir = @abs_aux_dir@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +lispdir = @lispdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AUTOMAKE_OPTIONS = nostdinc +AM_CPPFLAGS = -I$(top_srcdir)/lib -I../lib +AM_CFLAGS = $(WARN_CFLAGS) $(WERROR_CFLAGS) +m4_SOURCES = m4.h m4.c builtin.c debug.c eval.c format.c freeze.c input.c \ +macro.c output.c path.c symtab.c + +m4_LDADD = ../lib/libm4.a $(LIBM4_LIBDEPS) $(LIBCSTACK) $(LIBTHREAD) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p; \ + then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) +m4$(EXEEXT): $(m4_OBJECTS) $(m4_DEPENDENCIES) + @rm -f m4$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(m4_OBJECTS) $(m4_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/builtin.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/debug.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/eval.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/format.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/freeze.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/input.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/m4.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/macro.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/output.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/path.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/symtab.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@am__fastdepCC_FALSE@ $(AM_V_CC) @AM_BACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + set x; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in files) print i; }; }'`; \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic ctags distclean distclean-compile \ + distclean-generic distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags uninstall \ + uninstall-am uninstall-binPROGRAMS + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/builtin.c b/src/builtin.c new file mode 100644 index 0000000..632ef79 --- /dev/null +++ b/src/builtin.c @@ -0,0 +1,2257 @@ +/* GNU m4 -- A simple macro processor + + Copyright (C) 1989-1994, 2000, 2004, 2006-2011 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 "execute.h" +#include "memchr2.h" +#include "regex.h" +#include "spawn-pipe.h" +#include "wait-process.h" + +#define ARG(i) (argc > (i) ? TOKEN_DATA_TEXT (argv[i]) : "") + +/* 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, token_data **) + +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_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 const 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, false, true, m4_defn }, + { "divert", false, false, false, m4_divert }, + { "divnum", false, false, false, m4_divnum }, + { "dnl", false, false, false, m4_dnl }, + { "dumpdef", false, false, 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, false, true, m4_ifdef }, + { "ifelse", false, false, 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, false, true, m4_m4wrap }, + { "maketemp", false, false, true, m4_maketemp }, + { "mkstemp", false, false, true, m4_mkstemp }, + { "patsubst", true, false, true, m4_patsubst }, + { "popdef", false, false, true, m4_popdef }, + { "pushdef", false, true, true, m4_pushdef }, + { "regexp", true, false, true, m4_regexp }, + { "shift", false, false, 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, false, false, m4_traceoff }, + { "traceon", false, false, false, m4_traceon }, + { "translit", false, false, true, m4_translit }, + { "undefine", false, false, 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__", "" }, +#endif +#if W32_NATIVE + { "windows", "__windows__", "" }, +#endif +#if OS2 + { "os2", "__os2__", "" }, +#endif +#if !UNIX && !W32_NATIVE && !OS2 +# 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 (STREQ (bp->name, name)) + return bp; + return bp + 1; +} + +/*----------------------------------------------------------------. +| Install a builtin macro with name NAME, bound to the C function | +| given in BP. MODE is SYMBOL_INSERT or SYMBOL_PUSHDEF. | +`----------------------------------------------------------------*/ + +void +define_builtin (const char *name, const builtin *bp, symbol_lookup mode) +{ + symbol *sym; + + sym = lookup_symbol (name, 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; + +/*----------------------------------------. +| 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), ¯o_sequence_buf); + if (msg != NULL) + { + M4ERROR ((EXIT_FAILURE, 0, + "--warn-macro-sequence: bad regular expression `%s': %s", + regexp, msg)); + } + re_set_registers (¯o_sequence_buf, ¯o_sequence_regs, + macro_sequence_regs.num_regs, + macro_sequence_regs.start, macro_sequence_regs.end); + macro_sequence_inuse = true; +} + +/*-----------------------------------------------------------. +| Free dynamic memory utilized by the macro sequence regular | +| expression during the define builtin. | +`-----------------------------------------------------------*/ +void +free_macro_sequence (void) +{ + free_pattern_buffer (¯o_sequence_buf, ¯o_sequence_regs); +} + +/*-----------------------------------------------------------------. +| Define a predefined or user-defined macro, with name NAME, and | +| expansion TEXT. MODE destinguishes between the "define" and the | +| "pushdef" case. It is also used from main. | +`-----------------------------------------------------------------*/ + +void +define_user_macro (const char *name, const char *text, symbol_lookup mode) +{ + symbol *s; + char *defn = xstrdup (text ? text : ""); + + s = lookup_symbol (name, mode); + if (SYMBOL_TYPE (s) == TOKEN_TEXT) + free (SYMBOL_TEXT (s)); + + SYMBOL_TYPE (s) = TOKEN_TEXT; + SYMBOL_TEXT (s) = defn; + + /* Implement --warn-macro-sequence. */ + if (macro_sequence_inuse && text) + { + regoff_t offset = 0; + size_t len = strlen (defn); + + while ((offset = re_search (¯o_sequence_buf, defn, len, offset, + len - offset, ¯o_sequence_regs)) >= 0) + { + /* Skip empty matches. */ + if (macro_sequence_regs.start[0] == macro_sequence_regs.end[0]) + offset++; + else + { + char tmp; + offset = macro_sequence_regs.end[0]; + tmp = defn[offset]; + defn[offset] = '\0'; + M4ERROR ((warning_status, 0, + "Warning: definition of `%s' contains sequence `%s'", + name, defn + macro_sequence_regs.start[0])); + defn[offset] = tmp; + } + } + if (offset == -2) + M4ERROR ((warning_status, 0, + "error checking --warn-macro-sequence for macro `%s'", + name)); + } +} + +/*-----------------------------------------------. +| 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) + { + if (prefix_all_builtins) + { + string = (char *) xmalloc (strlen (bp->name) + 4); + strcpy (string, "m4_"); + strcat (string, bp->name); + define_builtin (string, bp, SYMBOL_INSERT); + free (string); + } + else + define_builtin (bp->name, 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, pp->func, SYMBOL_INSERT); + } + else + { + if (pp->gnu_name != NULL) + define_user_macro (pp->gnu_name, pp->func, SYMBOL_INSERT); + } +} + +/*-------------------------------------------------------------------. +| Give friendly warnings if a builtin macro is passed an | +| inappropriate number of arguments. NAME is the macro name for | +| messages, ARGC is actual number of arguments, MIN is the minimum | +| number of acceptable arguments, negative if not applicable, MAX is | +| the maximum number, negative if not applicable. | +`-------------------------------------------------------------------*/ + +static bool +bad_argc (token_data *name, int argc, int min, int max) +{ + bool isbad = false; + + if (min > 0 && argc < min) + { + if (!suppress_warnings) + M4ERROR ((warning_status, 0, + "Warning: too few arguments to builtin `%s'", + TOKEN_DATA_TEXT (name))); + isbad = true; + } + else if (max > 0 && argc > max && !suppress_warnings) + M4ERROR ((warning_status, 0, + "Warning: excess arguments to builtin `%s' ignored", + TOKEN_DATA_TEXT (name))); + + return isbad; +} + +/*-----------------------------------------------------------------. +| The function numeric_arg () converts ARG to an int pointed to by | +| VALUEP. If the conversion fails, print error message for macro | +| MACRO. Return true iff conversion succeeds. | +`-----------------------------------------------------------------*/ + +static bool +numeric_arg (token_data *macro, const char *arg, int *valuep) +{ + char *endp; + + if (*arg == '\0') + { + *valuep = 0; + M4ERROR ((warning_status, 0, + "empty string treated as 0 in builtin `%s'", + TOKEN_DATA_TEXT (macro))); + } + else + { + errno = 0; + *valuep = strtol (arg, &endp, 10); + if (*endp != '\0') + { + M4ERROR ((warning_status, 0, + "non-numeric argument to builtin `%s'", + TOKEN_DATA_TEXT (macro))); + return false; + } + if (isspace (to_uchar (*arg))) + M4ERROR ((warning_status, 0, + "leading whitespace ignored in builtin `%s'", + TOKEN_DATA_TEXT (macro))); + else if (errno == ERANGE) + M4ERROR ((warning_status, 0, + "numeric overflow detected in builtin `%s'", + TOKEN_DATA_TEXT (macro))); + } + 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"; + +const char * +ntoa (int32_t value, int radix) +{ + bool negative; + uint32_t uvalue; + static char str[256]; + 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)); +} + +/*-------------------------------------------------------------------. +| Print ARGC arguments from the table ARGV to obstack OBS, separated | +| by SEP, and quoted by the current quotes if QUOTED is true. | +`-------------------------------------------------------------------*/ + +static void +dump_args (struct obstack *obs, int argc, token_data **argv, + const char *sep, bool quoted) +{ + int i; + size_t len = strlen (sep); + + for (i = 1; i < argc; i++) + { + if (i > 1) + obstack_grow (obs, sep, len); + if (quoted) + obstack_grow (obs, lquote.string, lquote.length); + obstack_grow (obs, TOKEN_DATA_TEXT (argv[i]), + strlen (TOKEN_DATA_TEXT (argv[i]))); + if (quoted) + obstack_grow (obs, rquote.string, rquote.length); + } +} + +/* 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, 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. */ + +/* The first section are macros for definining, 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, token_data **argv, symbol_lookup mode) +{ + const builtin *bp; + + if (bad_argc (argv[0], argc, 2, 3)) + return; + + if (TOKEN_DATA_TYPE (argv[1]) != TOKEN_TEXT) + { + M4ERROR ((warning_status, 0, + "Warning: %s: invalid macro name ignored", ARG (0))); + return; + } + + if (argc == 2) + { + define_user_macro (ARG (1), "", mode); + return; + } + + switch (TOKEN_DATA_TYPE (argv[2])) + { + case TOKEN_TEXT: + define_user_macro (ARG (1), ARG (2), mode); + break; + + case TOKEN_FUNC: + bp = find_builtin_by_addr (TOKEN_DATA_FUNC (argv[2])); + if (bp == NULL) + return; + else + define_builtin (ARG (1), bp, mode); + break; + + case TOKEN_VOID: + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: bad token data type in define_macro ()")); + abort (); + } +} + +static void +m4_define (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + define_macro (argc, argv, SYMBOL_INSERT); +} + +static void +m4_undefine (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + int i; + if (bad_argc (argv[0], argc, 2, -1)) + return; + for (i = 1; i < argc; i++) + lookup_symbol (ARG (i), SYMBOL_DELETE); +} + +static void +m4_pushdef (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + define_macro (argc, argv, SYMBOL_PUSHDEF); +} + +static void +m4_popdef (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + int i; + if (bad_argc (argv[0], argc, 2, -1)) + return; + for (i = 1; i < argc; i++) + lookup_symbol (ARG (i), SYMBOL_POPDEF); +} + +/*---------------------. +| Conditionals of m4. | +`---------------------*/ + +static void +m4_ifdef (struct obstack *obs, int argc, token_data **argv) +{ + symbol *s; + const char *result; + + if (bad_argc (argv[0], argc, 3, 4)) + return; + s = lookup_symbol (ARG (1), SYMBOL_LOOKUP); + + if (s != NULL && SYMBOL_TYPE (s) != TOKEN_VOID) + result = ARG (2); + else if (argc >= 4) + result = ARG (3); + else + result = NULL; + + if (result != NULL) + obstack_grow (obs, result, strlen (result)); +} + +static void +m4_ifelse (struct obstack *obs, int argc, token_data **argv) +{ + const char *result; + token_data *me = argv[0]; + + if (argc == 2) + return; + + if (bad_argc (me, argc, 4, -1)) + return; + else + /* Diagnose excess arguments if 5, 8, 11, etc., actual arguments. */ + bad_argc (me, (argc + 2) % 3, -1, 1); + + argv++; + argc--; + + result = NULL; + while (result == NULL) + + if (STREQ (ARG (0), ARG (1))) + result = ARG (2); + + else + switch (argc) + { + case 3: + return; + + case 4: + case 5: + result = ARG (3); + break; + + default: + argc -= 3; + argv += 3; + } + + obstack_grow (obs, result, strlen (result)); +} + +/*-------------------------------------------------------------------. +| 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) +{ + return strcmp (SYMBOL_NAME (* (symbol *const *) s1), + SYMBOL_NAME (* (symbol *const *) s2)); +} + +/*-------------------------------------------------------------. +| 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, token_data **argv) +{ + symbol *s; + int i; + struct dump_symbol_data data; + const builtin *bp; + + 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++) + { + s = lookup_symbol (TOKEN_DATA_TEXT (argv[i]), SYMBOL_LOOKUP); + if (s != NULL && SYMBOL_TYPE (s) != TOKEN_VOID) + dump_symbol (s, &data); + else + M4ERROR ((warning_status, 0, + "undefined macro `%s'", TOKEN_DATA_TEXT (argv[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++) + { + DEBUG_PRINT1 ("%s:\t", SYMBOL_NAME (data.base[0])); + + switch (SYMBOL_TYPE (data.base[0])) + { + case TOKEN_TEXT: + if (debug_level & DEBUG_TRACE_QUOTE) + DEBUG_PRINT3 ("%s%s%s\n", + lquote.string, SYMBOL_TEXT (data.base[0]), rquote.string); + else + DEBUG_PRINT1 ("%s\n", SYMBOL_TEXT (data.base[0])); + break; + + case TOKEN_FUNC: + bp = find_builtin_by_addr (SYMBOL_FUNC (data.base[0])); + if (bp == NULL) + { + M4ERROR ((warning_status, 0, "\ +INTERNAL ERROR: builtin not found in builtin table")); + abort (); + } + DEBUG_PRINT1 ("<%s>\n", bp->name); + break; + + case TOKEN_VOID: + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: bad token data type in m4_dumpdef ()")); + abort (); + break; + } + } +} + +/*-----------------------------------------------------------------. +| 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, token_data **argv) +{ + const builtin *bp; + const char *name; + + if (bad_argc (argv[0], argc, 2, -1)) + return; + if (TOKEN_DATA_TYPE (argv[1]) != TOKEN_TEXT) + { + M4ERROR ((warning_status, 0, + "Warning: %s: invalid macro name ignored", ARG (0))); + return; + } + + name = ARG (1); + bp = find_builtin_by_name (name); + if (bp->func == m4_placeholder) + M4ERROR ((warning_status, 0, + "undefined builtin `%s'", name)); + else + { + int i; + if (! bp->groks_macro_args) + for (i = 2; i < argc; i++) + if (TOKEN_DATA_TYPE (argv[i]) != TOKEN_TEXT) + { + TOKEN_DATA_TYPE (argv[i]) = TOKEN_TEXT; + TOKEN_DATA_TEXT (argv[i]) = (char *) ""; + } + bp->func (obs, argc - 1, argv + 1); + } +} + +/*-------------------------------------------------------------------. +| 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, token_data **argv) +{ + symbol *s; + const char *name; + + if (bad_argc (argv[0], argc, 2, -1)) + return; + if (TOKEN_DATA_TYPE (argv[1]) != TOKEN_TEXT) + { + M4ERROR ((warning_status, 0, + "Warning: %s: invalid macro name ignored", ARG (0))); + return; + } + + name = ARG (1); + s = lookup_symbol (name, SYMBOL_LOOKUP); + if (s == NULL || SYMBOL_TYPE (s) == TOKEN_VOID) + M4ERROR ((warning_status, 0, + "undefined macro `%s'", name)); + else + { + int i; + if (! SYMBOL_MACRO_ARGS (s)) + for (i = 2; i < argc; i++) + if (TOKEN_DATA_TYPE (argv[i]) != TOKEN_TEXT) + { + TOKEN_DATA_TYPE (argv[i]) = TOKEN_TEXT; + TOKEN_DATA_TEXT (argv[i]) = (char *) ""; + } + call_macro (s, argc - 1, argv + 1, obs); + } +} + +/*------------------------------------------------------------------. +| 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, token_data **argv) +{ + symbol *s; + builtin_func *b; + unsigned int i; + + if (bad_argc (argv[0], argc, 2, -1)) + return; + + assert (0 < argc && argc <= INT_MAX); + for (i = 1; i < (unsigned) argc; i++) + { + const char *arg = ARG((int) i); + s = lookup_symbol (arg, SYMBOL_LOOKUP); + if (s == NULL) + continue; + + switch (SYMBOL_TYPE (s)) + { + case TOKEN_TEXT: + obstack_grow (obs, lquote.string, lquote.length); + obstack_grow (obs, SYMBOL_TEXT (s), strlen (SYMBOL_TEXT (s))); + obstack_grow (obs, rquote.string, rquote.length); + break; + + case TOKEN_FUNC: + b = SYMBOL_FUNC (s); + if (b == m4_placeholder) + M4ERROR ((warning_status, 0, "\ +builtin `%s' requested by frozen file is not supported", arg)); + else if (argc != 2) + M4ERROR ((warning_status, 0, + "Warning: cannot concatenate builtin `%s'", + arg)); + else + push_macro (b); + break; + + case TOKEN_VOID: + /* Nothing to do for traced but undefined macro. */ + break; + + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: bad symbol type in m4_defn ()")); + abort (); + } + } +} + +/*--------------------------------------------------------------. +| This section contains macros to handle the builtins "syscmd", | +| "esyscmd" and "sysval". "esyscmd" is GNU specific. | +`--------------------------------------------------------------*/ + +/* Exit code from last "syscmd" command. */ +static int sysval; + +static void +m4_syscmd (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + const char *cmd = ARG (1); + int status; + int sig_status; + const char *prog_args[4] = { "sh", "-c" }; + if (bad_argc (argv[0], argc, 2, 2) || !*cmd) + { + /* The empty command is successful. */ + sysval = 0; + return; + } + + debug_flush_files (); +#if W32_NATIVE + if (strstr (SYSCMD_SHELL, "cmd")) + { + prog_args[0] = "cmd"; + prog_args[1] = "/c"; + } +#endif + prog_args[2] = cmd; + errno = 0; + status = execute (ARG (0), SYSCMD_SHELL, (char **) prog_args, false, + false, false, false, true, false, &sig_status); + if (sig_status) + { + assert (status == 127); + sysval = sig_status << 8; + } + else + { + if (status == 127 && errno) + M4ERROR ((warning_status, errno, "cannot run command `%s'", cmd)); + sysval = status; + } +} + +static void +m4_esyscmd (struct obstack *obs, int argc, token_data **argv) +{ + const char *cmd = ARG (1); + const char *prog_args[4] = { "sh", "-c" }; + pid_t child; + int fd; + FILE *pin; + int status; + int sig_status; + + if (bad_argc (argv[0], argc, 2, 2) || !*cmd) + { + /* The empty command is successful. */ + sysval = 0; + return; + } + + debug_flush_files (); +#if W32_NATIVE + if (strstr (SYSCMD_SHELL, "cmd")) + { + prog_args[0] = "cmd"; + prog_args[1] = "/c"; + } +#endif + prog_args[2] = cmd; + errno = 0; + child = create_pipe_in (ARG (0), SYSCMD_SHELL, (char **) prog_args, + NULL, false, true, false, &fd); + if (child == -1) + { + M4ERROR ((warning_status, errno, "cannot run command `%s'", cmd)); + sysval = 127; + return; + } + pin = fdopen (fd, "r"); + if (pin == NULL) + { + M4ERROR ((warning_status, errno, "cannot run command `%s'", cmd)); + sysval = 127; + close (fd); + return; + } + while (1) + { + size_t avail = obstack_room (obs); + size_t len; + if (!avail) + { + int ch = getc (pin); + if (ch == EOF) + break; + obstack_1grow (obs, ch); + continue; + } + len = fread (obstack_next_free (obs), 1, avail, pin); + if (len <= 0) + break; + obstack_blank_fast (obs, len); + } + if (ferror (pin) || fclose (pin)) + M4ERROR ((EXIT_FAILURE, errno, "cannot read pipe")); + errno = 0; + status = wait_subprocess (child, ARG (0), false, true, true, false, + &sig_status); + if (sig_status) + { + assert (status == 127); + sysval = sig_status << 8; + } + else + { + if (status == 127 && errno) + M4ERROR ((warning_status, errno, "cannot run command `%s'", cmd)); + sysval = status; + } +} + +static void +m4_sysval (struct obstack *obs, int argc M4_GNUC_UNUSED, + token_data **argv M4_GNUC_UNUSED) +{ + shipout_int (obs, 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, token_data **argv) +{ + int32_t value = 0; + int radix = 10; + int min = 1; + const char *s; + + if (bad_argc (argv[0], argc, 2, 4)) + return; + + if (*ARG (2) && !numeric_arg (argv[0], ARG (2), &radix)) + return; + + if (radix < 1 || radix > (int) strlen (digits)) + { + M4ERROR ((warning_status, 0, + "radix %d in builtin `%s' out of range", + radix, ARG (0))); + return; + } + + if (argc >= 4 && !numeric_arg (argv[0], ARG (3), &min)) + return; + if (min < 0) + { + M4ERROR ((warning_status, 0, + "negative width to builtin `%s'", ARG (0))); + return; + } + + if (!*ARG (1)) + M4ERROR ((warning_status, 0, + "empty string treated as 0 in builtin `%s'", ARG (0))); + else if (evaluate (ARG (1), &value)) + return; + + if (radix == 1) + { + if (value < 0) + { + obstack_1grow (obs, '-'); + value = -value; + } + /* This assumes 2's-complement for correctly handling INT_MIN. */ + while (min-- - value > 0) + obstack_1grow (obs, '0'); + while (value-- != 0) + obstack_1grow (obs, '1'); + obstack_1grow (obs, '\0'); + return; + } + + s = ntoa (value, radix); + + if (*s == '-') + { + obstack_1grow (obs, '-'); + s++; + } + for (min -= strlen (s); --min >= 0;) + obstack_1grow (obs, '0'); + + obstack_grow (obs, s, strlen (s)); +} + +static void +m4_incr (struct obstack *obs, int argc, token_data **argv) +{ + int value; + + if (bad_argc (argv[0], argc, 2, 2)) + return; + + if (!numeric_arg (argv[0], ARG (1), &value)) + return; + + shipout_int (obs, value + 1); +} + +static void +m4_decr (struct obstack *obs, int argc, token_data **argv) +{ + int value; + + if (bad_argc (argv[0], argc, 2, 2)) + return; + + if (!numeric_arg (argv[0], ARG (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 M4_GNUC_UNUSED, int argc, token_data **argv) +{ + int i = 0; + + if (bad_argc (argv[0], argc, 1, 2)) + return; + + if (argc >= 2 && !numeric_arg (argv[0], ARG (1), &i)) + return; + + make_diversion (i); +} + +/*-----------------------------------------------------. +| Expand to the current diversion number, -1 if none. | +`-----------------------------------------------------*/ + +static void +m4_divnum (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 1, 1)) + return; + 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 M4_GNUC_UNUSED, int argc, token_data **argv) +{ + int i, file; + FILE *fp; + char *endp; + + if (argc == 1) + undivert_all (); + else + for (i = 1; i < argc; i++) + { + file = strtol (ARG (i), &endp, 10); + if (*endp == '\0' && !isspace (to_uchar (*ARG (i)))) + insert_diversion (file); + else if (no_gnu_extensions) + M4ERROR ((warning_status, 0, + "non-numeric argument to builtin `%s'", ARG (0))); + else + { + fp = m4_path_search (ARG (i), NULL); + if (fp != NULL) + { + insert_file (fp); + if (fclose (fp) == EOF) + M4ERROR ((warning_status, errno, + "error undiverting `%s'", ARG (i))); + } + else + M4ERROR ((warning_status, errno, + "cannot undivert `%s'", ARG (i))); + } + } +} + +/* 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 M4_GNUC_UNUSED, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 1, 1)) + return; + + skip_line (); +} + +/*--------------------------------------------------------------------. +| 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, token_data **argv) +{ + if (bad_argc (argv[0], argc, 2, -1)) + return; + dump_args (obs, argc - 1, argv + 1, ",", true); +} + +/*--------------------------------------------------------------------------. +| Change the current quotes. The function set_quotes () lives in input.c. | +`--------------------------------------------------------------------------*/ + +static void +m4_changequote (struct obstack *obs M4_GNUC_UNUSED, int argc, + token_data **argv) +{ + if (bad_argc (argv[0], argc, 1, 3)) + return; + + /* Explicit NULL distinguishes between empty and missing argument. */ + set_quotes ((argc >= 2) ? TOKEN_DATA_TEXT (argv[1]) : NULL, + (argc >= 3) ? TOKEN_DATA_TEXT (argv[2]) : NULL); +} + +/*-----------------------------------------------------------------. +| Change the current comment delimiters. The function set_comment | +| () lives in input.c. | +`-----------------------------------------------------------------*/ + +static void +m4_changecom (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 1, 3)) + return; + + /* Explicit NULL distinguishes between empty and missing argument. */ + set_comment ((argc >= 2) ? TOKEN_DATA_TEXT (argv[1]) : NULL, + (argc >= 3) ? TOKEN_DATA_TEXT (argv[2]) : NULL); +} + +#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 M4_GNUC_UNUSED, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 2, 2)) + return; + + set_word_regexp (TOKEN_DATA_TEXT (argv[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 inaccessible files iff | +| SILENT is false. | +`---------------------------------------------------------------*/ + +static void +include (int argc, token_data **argv, bool silent) +{ + FILE *fp; + char *name; + + if (bad_argc (argv[0], argc, 2, 2)) + return; + + fp = m4_path_search (ARG (1), &name); + if (fp == NULL) + { + if (!silent) + { + M4ERROR ((warning_status, errno, "cannot open `%s'", ARG (1))); + retcode = EXIT_FAILURE; + } + return; + } + + push_file (fp, name, true); + free (name); +} + +/*------------------------------------------------. +| Include a file, complaining in case of errors. | +`------------------------------------------------*/ + +static void +m4_include (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + include (argc, argv, false); +} + +/*----------------------------------. +| Include a file, ignoring errors. | +`----------------------------------*/ + +static void +m4_sinclude (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **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 at 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 char *me, const char *pattern, + size_t len) +{ + int fd; + size_t 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, lquote.string, lquote.length); + obstack_grow (obs, pattern, len); + for (i = 0; len > 0 && i < 6; i++) + if (pattern[len - i - 1] != 'X') + break; + obstack_grow0 (obs, "XXXXXX", 6 - i); + name = (char *) obstack_base (obs) + lquote.length; + + errno = 0; + fd = mkstemp (name); + if (fd < 0) + { + M4ERROR ((0, errno, "%s: cannot create tempfile `%s'", me, pattern)); + obstack_free (obs, obstack_finish (obs)); + } + else + { + close (fd); + /* Remove NUL, then finish quote. */ + obstack_blank (obs, -1); + obstack_grow (obs, rquote.string, rquote.length); + } +} + +static void +m4_maketemp (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 2, 2)) + return; + 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); + int len = strlen (str); + int i; + int len2; + + M4ERROR ((warning_status, 0, "recommend using mkstemp instead")); + for (i = len; i > 1; i--) + if (str[i - 1] != 'X') + break; + obstack_grow (obs, str, i); + str = ntoa ((int32_t) getpid (), 10); + len2 = strlen (str); + if (len2 > len - i) + obstack_grow0 (obs, str + len2 - (len - i), len - i); + else + { + while (i++ < len - len2) + obstack_1grow (obs, '0'); + obstack_grow0 (obs, str, len2); + } + } + else + mkstemp_helper (obs, ARG (0), ARG (1), strlen (ARG (1))); +} + +static void +m4_mkstemp (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 2, 2)) + return; + mkstemp_helper (obs, ARG (0), ARG (1), strlen (ARG (1))); +} + +/*----------------------------------------. +| Print all arguments on standard error. | +`----------------------------------------*/ + +static void +m4_errprint (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 2, -1)) + return; + dump_args (obs, argc, argv, " ", false); + obstack_1grow (obs, '\0'); + debug_flush_files (); + xfprintf (stderr, "%s", (char *) obstack_finish (obs)); + fflush (stderr); +} + +static void +m4___file__ (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 1, 1)) + return; + obstack_grow (obs, lquote.string, lquote.length); + obstack_grow (obs, current_file, strlen (current_file)); + obstack_grow (obs, rquote.string, rquote.length); +} + +static void +m4___line__ (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 1, 1)) + return; + shipout_int (obs, current_line); +} + +static void +m4___program__ (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 1, 1)) + return; + obstack_grow (obs, lquote.string, lquote.length); + obstack_grow (obs, program_name, strlen (program_name)); + obstack_grow (obs, rquote.string, rquote.length); +} + +/* 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 exit status specified by the first | +| argument, or 0 if no arguments are present. | +`----------------------------------------------------------*/ + +static void M4_GNUC_NORETURN +m4_m4exit (struct obstack *obs M4_GNUC_UNUSED, int argc, token_data **argv) +{ + int exit_code = EXIT_SUCCESS; + + /* Warn on bad arguments, but still exit. */ + bad_argc (argv[0], argc, 1, 2); + if (argc >= 2 && !numeric_arg (argv[0], ARG (1), &exit_code)) + exit_code = EXIT_FAILURE; + if (exit_code < 0 || exit_code > 255) + { + M4ERROR ((warning_status, 0, + "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 (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 until EOF has been seen, allowing for user | +| specified cleanup action. GNU version saves all arguments, the | +| standard version only the first. | +`------------------------------------------------------------------*/ + +static void +m4_m4wrap (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 2, -1)) + return; + if (no_gnu_extensions) + obstack_grow (obs, ARG (1), strlen (ARG (1))); + else + dump_args (obs, argc, argv, " ", false); + obstack_1grow (obs, '\0'); + push_wrapup ((char *) obstack_finish (obs)); +} + +/* 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 enables 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_POPDEF); +} + +static void +m4_traceon (struct obstack *obs, int argc, token_data **argv) +{ + symbol *s; + int i; + + if (argc == 1) + hack_all_symbols (set_trace, obs); + else + for (i = 1; i < argc; i++) + { + s = lookup_symbol (ARG (i), SYMBOL_LOOKUP); + if (!s) + s = lookup_symbol (ARG (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 M4_GNUC_UNUSED, int argc, token_data **argv) +{ + symbol *s; + int i; + + if (argc == 1) + hack_all_symbols (set_trace, NULL); + else + for (i = 1; i < argc; i++) + { + s = lookup_symbol (TOKEN_DATA_TEXT (argv[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 M4_GNUC_UNUSED, int argc, token_data **argv) +{ + int new_debug_level; + int change_flag; + + if (bad_argc (argv[0], argc, 1, 2)) + return; + + if (argc == 1) + debug_level = 0; + else + { + if (ARG (1)[0] == '+' || ARG (1)[0] == '-') + { + change_flag = ARG (1)[0]; + new_debug_level = debug_decode (ARG (1) + 1); + } + else + { + change_flag = 0; + new_debug_level = debug_decode (ARG (1)); + } + + if (new_debug_level < 0) + M4ERROR ((warning_status, 0, + "Debugmode: bad debug flags: `%s'", ARG (1))); + 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; + + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: bad flag in m4_debugmode ()")); + abort (); + } + } + } +} + +/*-------------------------------------------------------------------------. +| 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 M4_GNUC_UNUSED, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 1, 2)) + return; + + if (argc == 1) + debug_set_output (NULL); + else if (!debug_set_output (ARG (1))) + M4ERROR ((warning_status, errno, + "cannot set debug file `%s'", ARG (1))); +} + +/* 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, token_data **argv) +{ + if (bad_argc (argv[0], argc, 2, 2)) + return; + shipout_int (obs, strlen (ARG (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, token_data **argv) +{ + const char *haystack; + const char *result; + int retval; + + if (bad_argc (argv[0], argc, 3, 3)) + { + /* builtin(`index') is blank, but index(`abc') is 0. */ + if (argc == 2) + shipout_int (obs, 0); + return; + } + + haystack = ARG (1); + result = strstr (haystack, ARG (2)); + retval = result ? result - haystack : -1; + + 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, token_data **argv) +{ + int start = 0; + int length, avail; + + if (bad_argc (argv[0], argc, 3, 4)) + { + /* builtin(`substr') is blank, but substr(`abc') is abc. */ + if (argc == 2) + obstack_grow (obs, ARG (1), strlen (ARG (1))); + return; + } + + length = avail = strlen (ARG (1)); + if (!numeric_arg (argv[0], ARG (2), &start)) + return; + + if (argc >= 4 && !numeric_arg (argv[0], ARG (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. | +`------------------------------------------------------------------*/ + +static const char * +expand_ranges (const char *s, struct obstack *obs) +{ + unsigned char from; + unsigned char to; + + for (from = '\0'; *s != '\0'; from = to_uchar (*s++)) + { + if (*s == '-' && from != '\0') + { + to = to_uchar (*++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 (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. | +`-----------------------------------------------------------------*/ + +static void +m4_translit (struct obstack *obs, int argc, token_data **argv) +{ + const char *data = ARG (1); + const char *from = ARG (2); + const char *to; + char map[UCHAR_MAX + 1]; + char found[UCHAR_MAX + 1]; + unsigned char ch; + + if (bad_argc (argv[0], argc, 3, 4) || !*data || !*from) + { + /* builtin(`translit') is blank, but translit(`abc') is abc. */ + if (2 <= argc) + obstack_grow (obs, data, strlen (data)); + return; + } + + to = ARG (3); + if (strchr (to, '-') != NULL) + { + to = expand_ranges (to, obs); + assert (to && *to); + } + + /* If there are only one or two bytes to replace, it is faster to + use memchr2. Using expand_ranges does nothing unless there are + at least three bytes. */ + if (!from[1] || !from[2]) + { + const char *p; + size_t len = strlen (data); + while ((p = (char *) memchr2 (data, from[0], from[1], len))) + { + obstack_grow (obs, data, p - data); + len -= p - data; + if (!len) + return; + data = p + 1; + len--; + if (*p == from[0] && to[0]) + obstack_1grow (obs, to[0]); + else if (*p == from[1] && to[0] && to[1]) + obstack_1grow (obs, to[1]); + } + obstack_grow (obs, data, len); + return; + } + + if (strchr (from, '-') != NULL) + { + from = expand_ranges (from, obs); + assert (from && *from); + } + + /* 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. */ + memset (map, 0, sizeof map); + memset (found, 0, sizeof found); + for ( ; (ch = *from) != '\0'; from++) + { + if (! found[ch]) + { + found[ch] = 1; + map[ch] = *to; + } + if (*to != '\0') + to++; + } + + for (data = ARG (1); (ch = *data) != '\0'; data++) + { + if (! found[ch]) + obstack_1grow (obs, ch); + else if (map[ch]) + obstack_1grow (obs, map[ch]); + } +} + +/*-------------------------------------------------------------------. +| Frontend for printf like formatting. The function format () lives | +| in the file format.c. | +`-------------------------------------------------------------------*/ + +static void +m4_format (struct obstack *obs, int argc, token_data **argv) +{ + if (bad_argc (argv[0], argc, 2, -1)) + return; + expand_format (obs, argc - 1, argv + 1); +} + +/*------------------------------------------------------------------. +| 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 char *victim, const char *repl, + struct re_registers *regs) +{ + int ch; + __re_size_t ind; + while (1) + { + const char *backslash = strchr (repl, '\\'); + if (!backslash) + { + obstack_grow (obs, repl, strlen (repl)); + return; + } + obstack_grow (obs, repl, backslash - repl); + repl = backslash; + ch = *++repl; + switch (ch) + { + case '0': + if (!substitute_warned) + { + M4ERROR ((warning_status, 0, "\ +Warning: \\0 will disappear, use \\& instead in replacements")); + substitute_warned = 1; + } + /* Fall through. */ + + case '&': + obstack_grow (obs, victim + regs->start[0], + regs->end[0] - regs->start[0]); + repl++; + break; + + case '1': case '2': case '3': case '4': case '5': case '6': + case '7': case '8': case '9': + ind = ch -= '0'; + if (regs->num_regs - 1 <= ind) + M4ERROR ((warning_status, 0, + "Warning: 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]); + repl++; + break; + + case '\0': + M4ERROR ((warning_status, 0, + "Warning: trailing \\ ignored in replacement")); + return; + + default: + obstack_1grow (obs, ch); + repl++; + 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 here is no match. Given a third | +| argument, it changes the expansion to this argument. | +`------------------------------------------------------------------*/ + +static void +m4_regexp (struct obstack *obs, int argc, token_data **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 (argv[0], argc, 3, 4)) + { + /* builtin(`regexp') is blank, but regexp(`abc') is 0. */ + if (argc == 2) + shipout_int (obs, 0); + return; + } + + victim = TOKEN_DATA_TEXT (argv[1]); + regexp = TOKEN_DATA_TEXT (argv[2]); + + init_pattern_buffer (&buf, ®s); + msg = re_compile_pattern (regexp, strlen (regexp), &buf); + + if (msg != NULL) + { + M4ERROR ((warning_status, 0, + "bad regular expression: `%s': %s", regexp, msg)); + free_pattern_buffer (&buf, ®s); + return; + } + + length = strlen (victim); + /* Avoid overhead of allocating regs if we won't use it. */ + startpos = re_search (&buf, victim, length, 0, length, + argc == 3 ? NULL : ®s); + + if (startpos == -2) + M4ERROR ((warning_status, 0, + "error matching regular expression `%s'", regexp)); + else if (argc == 3) + shipout_int (obs, startpos); + else if (startpos >= 0) + { + repl = TOKEN_DATA_TEXT (argv[3]); + substitute (obs, victim, repl, ®s); + } + + free_pattern_buffer (&buf, ®s); +} + +/*--------------------------------------------------------------------------. +| Substitute all matches of a regexp occuring 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, token_data **argv) +{ + const char *victim; /* first argument */ + const char *regexp; /* regular expression */ + + 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 */ + + if (bad_argc (argv[0], argc, 3, 4)) + { + /* builtin(`patsubst') is blank, but patsubst(`abc') is abc. */ + if (argc == 2) + obstack_grow (obs, ARG (1), strlen (ARG (1))); + return; + } + + regexp = TOKEN_DATA_TEXT (argv[2]); + + init_pattern_buffer (&buf, ®s); + msg = re_compile_pattern (regexp, strlen (regexp), &buf); + + if (msg != NULL) + { + M4ERROR ((warning_status, 0, + "bad regular expression `%s': %s", regexp, msg)); + free (buf.buffer); + return; + } + + victim = TOKEN_DATA_TEXT (argv[1]); + length = strlen (victim); + + offset = 0; + while (offset <= length) + { + matchpos = re_search (&buf, victim, length, + offset, length - offset, ®s); + 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) + M4ERROR ((warning_status, 0, + "error matching regular expression `%s'", regexp)); + 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, victim, ARG (3), ®s); + + /* 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]) + obstack_1grow (obs, victim[offset++]); + } + obstack_1grow (obs, '\0'); + + free_pattern_buffer (&buf, ®s); +} + +/* 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 M4_GNUC_UNUSED, int argc, + token_data **argv) +{ + M4ERROR ((warning_status, 0, "\ +builtin `%s' requested by frozen file is not supported", ARG (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, token_data **argv) +{ + const char *text = SYMBOL_TEXT (sym); + int i; + while (1) + { + const char *dollar = strchr (text, '$'); + if (!dollar) + { + obstack_grow (obs, text, strlen (text)); + return; + } + obstack_grow (obs, text, dollar - text); + text = dollar; + 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'; + } + else + { + for (i = 0; isdigit (to_uchar (*text)); text++) + i = i*10 + (*text - '0'); + } + if (i < argc) + obstack_grow (obs, TOKEN_DATA_TEXT (argv[i]), + strlen (TOKEN_DATA_TEXT (argv[i]))); + break; + + case '#': /* number of arguments */ + shipout_int (obs, argc - 1); + text++; + break; + + case '*': /* all arguments */ + case '@': /* ... same, but quoted */ + dump_args (obs, argc, argv, ",", *text == '@'); + text++; + break; + + default: + obstack_1grow (obs, '$'); + break; + } + } +} diff --git a/src/debug.c b/src/debug.c new file mode 100644 index 0000000..0fef85c --- /dev/null +++ b/src/debug.c @@ -0,0 +1,444 @@ +/* GNU m4 -- A simple macro processor + + Copyright (C) 1991-1994, 2004, 2006-2007, 2009-2011 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 "m4.h" + +#include <stdarg.h> +#include <sys/stat.h> + +/* File for debugging output. */ +FILE *debug = NULL; + +/* Obstack for trace messages. */ +static struct obstack trace; + +extern int expansion_level; + +static void debug_set_file (FILE *); + +/*----------------------------------. +| Initialise the debugging module. | +`----------------------------------*/ + +void +debug_init (void) +{ + debug_set_file (stderr); + obstack_init (&trace); +} + +/*-----------------------------------------------------------------. +| Function to decode the debugging flags OPTS. Used by main while | +| processing option -d, and by the builtin debugmode (). | +`-----------------------------------------------------------------*/ + +int +debug_decode (const char *opts) +{ + int level; + + if (opts == NULL || *opts == '\0') + level = DEBUG_TRACE_DEFAULT; + else + { + for (level = 0; *opts; opts++) + { + switch (*opts) + { + case 'a': + level |= DEBUG_TRACE_ARGS; + break; + + case 'e': + level |= DEBUG_TRACE_EXPANSION; + break; + + case 'q': + level |= DEBUG_TRACE_QUOTE; + break; + + case 't': + level |= DEBUG_TRACE_ALL; + break; + + case 'l': + level |= DEBUG_TRACE_LINE; + break; + + case 'f': + level |= DEBUG_TRACE_FILE; + break; + + case 'p': + level |= DEBUG_TRACE_PATH; + break; + + case 'c': + level |= DEBUG_TRACE_CALL; + break; + + case 'i': + level |= DEBUG_TRACE_INPUT; + break; + + case 'x': + level |= DEBUG_TRACE_CALLID; + break; + + case 'V': + level |= DEBUG_TRACE_VERBOSE; + break; + + default: + return -1; + } + } + } + + /* This is to avoid screwing up the trace output due to changes in the + debug_level. */ + + obstack_free (&trace, obstack_finish (&trace)); + + return level; +} + +/*-----------------------------------------------------------------. +| Change the debug output stream to FP. If the underlying file is | +| the same as stdout, use stdout instead so that debug messages | +| appear in the correct relative position. | +`-----------------------------------------------------------------*/ + +static void +debug_set_file (FILE *fp) +{ + struct stat stdout_stat, debug_stat; + + if (debug != NULL && debug != stderr && debug != stdout + && close_stream (debug) != 0) + { + M4ERROR ((warning_status, errno, "error writing to debug stream")); + retcode = EXIT_FAILURE; + } + debug = fp; + + if (debug != NULL && debug != stdout) + { + if (fstat (STDOUT_FILENO, &stdout_stat) < 0) + return; + if (fstat (fileno (debug), &debug_stat) < 0) + return; + + /* mingw has a bug where fstat on a regular file reports st_ino + of 0. On normal system, st_ino should never be 0. */ + if (stdout_stat.st_ino == debug_stat.st_ino + && stdout_stat.st_dev == debug_stat.st_dev + && stdout_stat.st_ino != 0) + { + if (debug != stderr && close_stream (debug) != 0) + { + M4ERROR ((warning_status, errno, + "error writing to debug stream")); + retcode = EXIT_FAILURE; + } + debug = stdout; + } + } +} + +/*-----------------------------------------------------------. +| Serialize files. Used before executing a system command. | +`-----------------------------------------------------------*/ + +void +debug_flush_files (void) +{ + fflush (stdout); + fflush (stderr); + if (debug != NULL && debug != stdout && debug != stderr) + fflush (debug); + /* 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); + } +} + +/*--------------------------------------------------------------. +| Change the debug output to file NAME. If NAME is NULL, debug | +| output is reverted to stderr, and if empty, debug output is | +| discarded. Return true iff the output stream was changed. | +`--------------------------------------------------------------*/ + +bool +debug_set_output (const char *name) +{ + FILE *fp; + + if (name == NULL) + debug_set_file (stderr); + else if (*name == '\0') + debug_set_file (NULL); + else + { + fp = fopen (name, "a"); + if (fp == NULL) + return false; + + if (set_cloexec_flag (fileno (fp), true) != 0) + M4ERROR ((warning_status, errno, + "Warning: cannot protect debug file across forks")); + debug_set_file (fp); + } + return true; +} + +/*--------------------------------------------------------------. +| Print the header of a one-line debug message, starting by "m4 | +| debug". | +`--------------------------------------------------------------*/ + +void +debug_message_prefix (void) +{ + xfprintf (debug, "m4debug:"); + if (current_line) + { + if (debug_level & DEBUG_TRACE_FILE) + xfprintf (debug, "%s:", current_file); + if (debug_level & DEBUG_TRACE_LINE) + xfprintf (debug, "%d:", current_line); + } + putc (' ', debug); +} + +/* The rest of this file contains the functions for macro tracing output. + All tracing output for a macro call is collected on an obstack TRACE, + and printed whenever the line is complete. This prevents tracing + output from interfering with other debug messages generated by the + various builtins. */ + +/*------------------------------------------------------------------. +| Tracing output is formatted here, by a simplified | +| printf-to-obstack function trace_format (). Understands only %S, | +| %s, %d, %l (optional left quote) and %r (optional right quote). | +`------------------------------------------------------------------*/ + +static void +trace_format (const char *fmt, ...) +{ + va_list args; + char ch; + + int d; + const char *s; + int slen; + int maxlen; + + va_start (args, fmt); + + while (true) + { + while ((ch = *fmt++) != '\0' && ch != '%') + obstack_1grow (&trace, ch); + + if (ch == '\0') + break; + + maxlen = 0; + switch (*fmt++) + { + case 'S': + maxlen = max_debug_argument_length; + /* fall through */ + + case 's': + s = va_arg (args, const char *); + break; + + case 'l': + s = (debug_level & DEBUG_TRACE_QUOTE) ? lquote.string : ""; + break; + + case 'r': + s = (debug_level & DEBUG_TRACE_QUOTE) ? rquote.string : ""; + break; + + case 'd': + d = va_arg (args, int); + s = ntoa (d, 10); + break; + + default: + s = ""; + break; + } + + slen = strlen (s); + if (maxlen == 0 || maxlen > slen) + obstack_grow (&trace, s, slen); + else + { + obstack_grow (&trace, s, maxlen); + obstack_grow (&trace, "...", 3); + } + } + + va_end (args); +} + +/*------------------------------------------------------------------. +| Format the standard header attached to all tracing output lines. | +`------------------------------------------------------------------*/ + +static void +trace_header (int id) +{ + trace_format ("m4trace:"); + if (current_line) + { + if (debug_level & DEBUG_TRACE_FILE) + trace_format ("%s:", current_file); + if (debug_level & DEBUG_TRACE_LINE) + trace_format ("%d:", current_line); + } + trace_format (" -%d- ", expansion_level); + if (debug_level & DEBUG_TRACE_CALLID) + trace_format ("id %d: ", id); +} + +/*----------------------------------------------------. +| Print current tracing line, and clear the obstack. | +`----------------------------------------------------*/ + +static void +trace_flush (void) +{ + char *line; + + obstack_1grow (&trace, '\0'); + line = (char *) obstack_finish (&trace); + DEBUG_PRINT1 ("%s\n", line); + obstack_free (&trace, line); +} + +/*-------------------------------------------------------------. +| Do pre-argument-collction tracing for macro NAME. Used from | +| expand_macro (). | +`-------------------------------------------------------------*/ + +void +trace_prepre (const char *name, int id) +{ + trace_header (id); + trace_format ("%s ...", name); + trace_flush (); +} + +/*--------------------------------------------------------------. +| Format the parts of a trace line, that can be made before the | +| macro is actually expanded. Used from expand_macro (). | +`--------------------------------------------------------------*/ + +void +trace_pre (const char *name, int id, int argc, token_data **argv) +{ + int i; + const builtin *bp; + + trace_header (id); + trace_format ("%s", name); + + if (argc > 1 && (debug_level & DEBUG_TRACE_ARGS)) + { + trace_format ("("); + + for (i = 1; i < argc; i++) + { + if (i != 1) + trace_format (", "); + + switch (TOKEN_DATA_TYPE (argv[i])) + { + case TOKEN_TEXT: + trace_format ("%l%S%r", TOKEN_DATA_TEXT (argv[i])); + break; + + case TOKEN_FUNC: + bp = find_builtin_by_addr (TOKEN_DATA_FUNC (argv[i])); + if (bp == NULL) + { + M4ERROR ((warning_status, 0, "\ +INTERNAL ERROR: builtin not found in builtin table! (trace_pre ())")); + abort (); + } + trace_format ("<%s>", bp->name); + break; + + case TOKEN_VOID: + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: bad token data type (trace_pre ())")); + abort (); + } + + } + trace_format (")"); + } + + if (debug_level & DEBUG_TRACE_CALL) + { + trace_format (" -> ???"); + trace_flush (); + } +} + +/*-------------------------------------------------------------------. +| Format the final part of a trace line and print it all. Used from | +| expand_macro (). | +`-------------------------------------------------------------------*/ + +void +trace_post (const char *name, int id, int argc, const char *expanded) +{ + if (debug_level & DEBUG_TRACE_CALL) + { + trace_header (id); + trace_format ("%s%s", name, (argc > 1) ? "(...)" : ""); + } + + if (expanded && (debug_level & DEBUG_TRACE_EXPANSION)) + trace_format (" -> %l%S%r", expanded); + trace_flush (); +} diff --git a/src/eval.c b/src/eval.c new file mode 100644 index 0000000..b74bce8 --- /dev/null +++ b/src/eval.c @@ -0,0 +1,855 @@ +/* GNU m4 -- A simple macro processor + + Copyright (C) 1989-1994, 2006-2007, 2009-2011 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/>. +*/ + +/* This file contains the functions to evaluate integer expressions for + the "eval" macro. It is a little, fairly self-contained module, with + its own scanner, and a recursive descent parser. The only entry point + is evaluate (). */ + +#include "m4.h" + +/* Evaluates token types. */ + +typedef enum eval_token + { + ERROR, BADOP, + PLUS, MINUS, + EXPONENT, + TIMES, DIVIDE, MODULO, + ASSIGN, EQ, NOTEQ, GT, GTEQ, LS, LSEQ, + LSHIFT, RSHIFT, + LNOT, LAND, LOR, + NOT, AND, OR, XOR, + LEFTP, RIGHTP, + NUMBER, EOTEXT + } +eval_token; + +/* Error types. */ + +typedef enum eval_error + { + NO_ERROR, + DIVIDE_ZERO, + MODULO_ZERO, + NEGATIVE_EXPONENT, + /* All errors prior to SYNTAX_ERROR can be ignored in a dead + branch of && and ||. All errors after are just more details + about a syntax error. */ + SYNTAX_ERROR, + MISSING_RIGHT, + UNKNOWN_INPUT, + EXCESS_INPUT, + INVALID_OPERATOR + } +eval_error; + +static eval_error logical_or_term (eval_token, int32_t *); +static eval_error logical_and_term (eval_token, int32_t *); +static eval_error or_term (eval_token, int32_t *); +static eval_error xor_term (eval_token, int32_t *); +static eval_error and_term (eval_token, int32_t *); +static eval_error equality_term (eval_token, int32_t *); +static eval_error cmp_term (eval_token, int32_t *); +static eval_error shift_term (eval_token, int32_t *); +static eval_error add_term (eval_token, int32_t *); +static eval_error mult_term (eval_token, int32_t *); +static eval_error exp_term (eval_token, int32_t *); +static eval_error unary_term (eval_token, int32_t *); +static eval_error simple_term (eval_token, int32_t *); + +/*--------------------. +| Lexical functions. | +`--------------------*/ + +/* Pointer to next character of input text. */ +static const char *eval_text; + +/* Value of eval_text, from before last call of eval_lex (). This is so we + can back up, if we have read too much. */ +static const char *last_text; + +static void +eval_init_lex (const char *text) +{ + eval_text = text; + last_text = NULL; +} + +static void +eval_undo (void) +{ + eval_text = last_text; +} + +/* VAL is numerical value, if any. */ + +static eval_token +eval_lex (int32_t *val) +{ + while (isspace (to_uchar (*eval_text))) + eval_text++; + + last_text = eval_text; + + if (*eval_text == '\0') + return EOTEXT; + + if (isdigit (to_uchar (*eval_text))) + { + int base, digit; + + if (*eval_text == '0') + { + eval_text++; + switch (*eval_text) + { + case 'x': + case 'X': + base = 16; + eval_text++; + break; + + case 'b': + case 'B': + base = 2; + eval_text++; + break; + + case 'r': + case 'R': + base = 0; + eval_text++; + while (isdigit (to_uchar (*eval_text)) && base <= 36) + base = 10 * base + *eval_text++ - '0'; + if (base == 0 || base > 36 || *eval_text != ':') + return ERROR; + eval_text++; + break; + + default: + base = 8; + } + } + else + base = 10; + + /* FIXME - this calculation can overflow. Consider xstrtol. */ + *val = 0; + for (; *eval_text; eval_text++) + { + if (isdigit (to_uchar (*eval_text))) + digit = *eval_text - '0'; + else if (islower (to_uchar (*eval_text))) + digit = *eval_text - 'a' + 10; + else if (isupper (to_uchar (*eval_text))) + digit = *eval_text - 'A' + 10; + else + break; + + if (base == 1) + { + if (digit == 1) + (*val)++; + else if (digit == 0 && !*val) + continue; + else + break; + } + else if (digit >= base) + break; + else + *val = *val * base + digit; + } + return NUMBER; + } + + switch (*eval_text++) + { + case '+': + if (*eval_text == '+' || *eval_text == '=') + return BADOP; + return PLUS; + case '-': + if (*eval_text == '-' || *eval_text == '=') + return BADOP; + return MINUS; + case '*': + if (*eval_text == '*') + { + eval_text++; + return EXPONENT; + } + else if (*eval_text == '=') + return BADOP; + return TIMES; + case '/': + if (*eval_text == '=') + return BADOP; + return DIVIDE; + case '%': + if (*eval_text == '=') + return BADOP; + return MODULO; + case '=': + if (*eval_text == '=') + { + eval_text++; + return EQ; + } + return ASSIGN; + case '!': + if (*eval_text == '=') + { + eval_text++; + return NOTEQ; + } + return LNOT; + case '>': + if (*eval_text == '=') + { + eval_text++; + return GTEQ; + } + else if (*eval_text == '>') + { + if (*++eval_text == '=') + return BADOP; + return RSHIFT; + } + return GT; + case '<': + if (*eval_text == '=') + { + eval_text++; + return LSEQ; + } + else if (*eval_text == '<') + { + if (*++eval_text == '=') + return BADOP; + return LSHIFT; + } + return LS; + case '^': + if (*eval_text == '=') + return BADOP; + return XOR; + case '~': + return NOT; + case '&': + if (*eval_text == '&') + { + eval_text++; + return LAND; + } + else if (*eval_text == '=') + return BADOP; + return AND; + case '|': + if (*eval_text == '|') + { + eval_text++; + return LOR; + } + else if (*eval_text == '=') + return BADOP; + return OR; + case '(': + return LEFTP; + case ')': + return RIGHTP; + default: + return ERROR; + } +} + +/*---------------------------------------. +| Main entry point, called from "eval". | +`---------------------------------------*/ + +bool +evaluate (const char *expr, int32_t *val) +{ + eval_token et; + eval_error err; + + eval_init_lex (expr); + et = eval_lex (val); + err = logical_or_term (et, val); + + if (err == NO_ERROR && *eval_text != '\0') + { + if (eval_lex (val) == BADOP) + err = INVALID_OPERATOR; + else + err = EXCESS_INPUT; + } + + switch (err) + { + case NO_ERROR: + break; + + case MISSING_RIGHT: + M4ERROR ((warning_status, 0, + "bad expression in eval (missing right parenthesis): %s", + expr)); + break; + + case SYNTAX_ERROR: + M4ERROR ((warning_status, 0, + "bad expression in eval: %s", expr)); + break; + + case UNKNOWN_INPUT: + M4ERROR ((warning_status, 0, + "bad expression in eval (bad input): %s", expr)); + break; + + case EXCESS_INPUT: + M4ERROR ((warning_status, 0, + "bad expression in eval (excess input): %s", expr)); + break; + + case INVALID_OPERATOR: + M4ERROR ((warning_status, 0, + "invalid operator in eval: %s", expr)); + retcode = EXIT_FAILURE; + break; + + case DIVIDE_ZERO: + M4ERROR ((warning_status, 0, + "divide by zero in eval: %s", expr)); + break; + + case MODULO_ZERO: + M4ERROR ((warning_status, 0, + "modulo by zero in eval: %s", expr)); + break; + + case NEGATIVE_EXPONENT: + M4ERROR ((warning_status, 0, + "negative exponent in eval: %s", expr)); + break; + + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: bad error code in evaluate ()")); + abort (); + } + + return err != NO_ERROR; +} + +/*---------------------------. +| Recursive descent parser. | +`---------------------------*/ + +static eval_error +logical_or_term (eval_token et, int32_t *v1) +{ + int32_t v2; + eval_error er; + + if ((er = logical_and_term (et, v1)) != NO_ERROR) + return er; + + while ((et = eval_lex (&v2)) == LOR) + { + et = eval_lex (&v2); + if (et == ERROR) + return UNKNOWN_INPUT; + + /* Implement short-circuiting of valid syntax. */ + er = logical_and_term (et, &v2); + if (er == NO_ERROR) + *v1 = *v1 || v2; + else if (*v1 != 0 && er < SYNTAX_ERROR) + *v1 = 1; + else + return er; + } + if (et == ERROR) + return UNKNOWN_INPUT; + + eval_undo (); + return NO_ERROR; +} + +static eval_error +logical_and_term (eval_token et, int32_t *v1) +{ + int32_t v2; + eval_error er; + + if ((er = or_term (et, v1)) != NO_ERROR) + return er; + + while ((et = eval_lex (&v2)) == LAND) + { + et = eval_lex (&v2); + if (et == ERROR) + return UNKNOWN_INPUT; + + /* Implement short-circuiting of valid syntax. */ + er = or_term (et, &v2); + if (er == NO_ERROR) + *v1 = *v1 && v2; + else if (*v1 == 0 && er < SYNTAX_ERROR) + ; /* v1 is already 0 */ + else + return er; + } + if (et == ERROR) + return UNKNOWN_INPUT; + + eval_undo (); + return NO_ERROR; +} + +static eval_error +or_term (eval_token et, int32_t *v1) +{ + int32_t v2; + eval_error er; + + if ((er = xor_term (et, v1)) != NO_ERROR) + return er; + + while ((et = eval_lex (&v2)) == OR) + { + et = eval_lex (&v2); + if (et == ERROR) + return UNKNOWN_INPUT; + + if ((er = xor_term (et, &v2)) != NO_ERROR) + return er; + + *v1 |= v2; + } + if (et == ERROR) + return UNKNOWN_INPUT; + + eval_undo (); + return NO_ERROR; +} + +static eval_error +xor_term (eval_token et, int32_t *v1) +{ + int32_t v2; + eval_error er; + + if ((er = and_term (et, v1)) != NO_ERROR) + return er; + + while ((et = eval_lex (&v2)) == XOR) + { + et = eval_lex (&v2); + if (et == ERROR) + return UNKNOWN_INPUT; + + if ((er = and_term (et, &v2)) != NO_ERROR) + return er; + + *v1 ^= v2; + } + if (et == ERROR) + return UNKNOWN_INPUT; + + eval_undo (); + return NO_ERROR; +} + +static eval_error +and_term (eval_token et, int32_t *v1) +{ + int32_t v2; + eval_error er; + + if ((er = equality_term (et, v1)) != NO_ERROR) + return er; + + while ((et = eval_lex (&v2)) == AND) + { + et = eval_lex (&v2); + if (et == ERROR) + return UNKNOWN_INPUT; + + if ((er = equality_term (et, &v2)) != NO_ERROR) + return er; + + *v1 &= v2; + } + if (et == ERROR) + return UNKNOWN_INPUT; + + eval_undo (); + return NO_ERROR; +} + +static eval_error +equality_term (eval_token et, int32_t *v1) +{ + eval_token op; + int32_t v2; + eval_error er; + + if ((er = cmp_term (et, v1)) != NO_ERROR) + return er; + + /* In the 1.4.x series, we maintain the traditional behavior that + '=' is a synonym for '=='; however, this is contrary to POSIX and + we hope to convert '=' to mean assignment in 2.0. */ + while ((op = eval_lex (&v2)) == EQ || op == NOTEQ || op == ASSIGN) + { + et = eval_lex (&v2); + if (et == ERROR) + return UNKNOWN_INPUT; + + if ((er = cmp_term (et, &v2)) != NO_ERROR) + return er; + + if (op == ASSIGN) + { + M4ERROR ((warning_status, 0, "\ +Warning: recommend ==, not =, for equality operator")); + op = EQ; + } + *v1 = (op == EQ) == (*v1 == v2); + } + if (op == ERROR) + return UNKNOWN_INPUT; + + eval_undo (); + return NO_ERROR; +} + +static eval_error +cmp_term (eval_token et, int32_t *v1) +{ + eval_token op; + int32_t v2; + eval_error er; + + if ((er = shift_term (et, v1)) != NO_ERROR) + return er; + + while ((op = eval_lex (&v2)) == GT || op == GTEQ + || op == LS || op == LSEQ) + { + + et = eval_lex (&v2); + if (et == ERROR) + return UNKNOWN_INPUT; + + if ((er = shift_term (et, &v2)) != NO_ERROR) + return er; + + switch (op) + { + case GT: + *v1 = *v1 > v2; + break; + + case GTEQ: + *v1 = *v1 >= v2; + break; + + case LS: + *v1 = *v1 < v2; + break; + + case LSEQ: + *v1 = *v1 <= v2; + break; + + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: bad comparison operator in cmp_term ()")); + abort (); + } + } + if (op == ERROR) + return UNKNOWN_INPUT; + + eval_undo (); + return NO_ERROR; +} + +static eval_error +shift_term (eval_token et, int32_t *v1) +{ + eval_token op; + int32_t v2; + uint32_t u1; + eval_error er; + + if ((er = add_term (et, v1)) != NO_ERROR) + return er; + + while ((op = eval_lex (&v2)) == LSHIFT || op == RSHIFT) + { + + et = eval_lex (&v2); + if (et == ERROR) + return UNKNOWN_INPUT; + + if ((er = add_term (et, &v2)) != NO_ERROR) + return er; + + /* 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 32-bit + wrap-around semantics. This code assumes that the + implementation-defined overflow when casting unsigned to + signed is a silent twos-complement wrap-around. */ + switch (op) + { + case LSHIFT: + u1 = *v1; + u1 <<= (uint32_t) (v2 & 0x1f); + *v1 = u1; + break; + + case RSHIFT: + u1 = *v1 < 0 ? ~*v1 : *v1; + u1 >>= (uint32_t) (v2 & 0x1f); + *v1 = *v1 < 0 ? ~u1 : u1; + break; + + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: bad shift operator in shift_term ()")); + abort (); + } + } + if (op == ERROR) + return UNKNOWN_INPUT; + + eval_undo (); + return NO_ERROR; +} + +static eval_error +add_term (eval_token et, int32_t *v1) +{ + eval_token op; + int32_t v2; + eval_error er; + + if ((er = mult_term (et, v1)) != NO_ERROR) + return er; + + while ((op = eval_lex (&v2)) == PLUS || op == MINUS) + { + et = eval_lex (&v2); + if (et == ERROR) + return UNKNOWN_INPUT; + + if ((er = mult_term (et, &v2)) != NO_ERROR) + return er; + + /* Minimize undefined C behavior on overflow. This code assumes + that the implementation-defined overflow when casting + unsigned to signed is a silent twos-complement + wrap-around. */ + if (op == PLUS) + *v1 = (int32_t) ((uint32_t) *v1 + (uint32_t) v2); + else + *v1 = (int32_t) ((uint32_t) *v1 - (uint32_t) v2); + } + if (op == ERROR) + return UNKNOWN_INPUT; + + eval_undo (); + return NO_ERROR; +} + +static eval_error +mult_term (eval_token et, int32_t *v1) +{ + eval_token op; + int32_t v2; + eval_error er; + + if ((er = exp_term (et, v1)) != NO_ERROR) + return er; + + while ((op = eval_lex (&v2)) == TIMES || op == DIVIDE || op == MODULO) + { + et = eval_lex (&v2); + if (et == ERROR) + return UNKNOWN_INPUT; + + if ((er = exp_term (et, &v2)) != NO_ERROR) + return er; + + /* Minimize undefined C behavior on overflow. This code assumes + that the implementation-defined overflow when casting + unsigned to signed is a silent twos-complement + wrap-around. */ + switch (op) + { + case TIMES: + *v1 = (int32_t) ((uint32_t) *v1 * (uint32_t) v2); + break; + + case DIVIDE: + if (v2 == 0) + return DIVIDE_ZERO; + else if (v2 == -1) + /* Avoid overflow, and the x86 SIGFPE on INT_MIN / -1. */ + *v1 = (int32_t) -(uint32_t) *v1; + else + *v1 /= v2; + break; + + case MODULO: + if (v2 == 0) + return MODULO_ZERO; + else if (v2 == -1) + /* Avoid the x86 SIGFPE on INT_MIN % -1. */ + *v1 = 0; + else + *v1 %= v2; + break; + + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: bad operator in mult_term ()")); + abort (); + } + } + if (op == ERROR) + return UNKNOWN_INPUT; + + eval_undo (); + return NO_ERROR; +} + +static eval_error +exp_term (eval_token et, int32_t *v1) +{ + uint32_t result; + int32_t v2; + eval_error er; + + if ((er = unary_term (et, v1)) != NO_ERROR) + return er; + + while ((et = eval_lex (&v2)) == EXPONENT) + { + et = eval_lex (&v2); + if (et == ERROR) + return UNKNOWN_INPUT; + + if ((er = exp_term (et, &v2)) != NO_ERROR) + return er; + + /* Minimize undefined C behavior on overflow. This code assumes + that the implementation-defined overflow when casting + unsigned to signed is a silent twos-complement + wrap-around. */ + result = 1; + if (v2 < 0) + return NEGATIVE_EXPONENT; + if (*v1 == 0 && v2 == 0) + return DIVIDE_ZERO; + while (v2-- > 0) + result *= (uint32_t) *v1; + *v1 = result; + } + if (et == ERROR) + return UNKNOWN_INPUT; + + eval_undo (); + return NO_ERROR; +} + +static eval_error +unary_term (eval_token et, int32_t *v1) +{ + eval_error er; + + if (et == PLUS || et == MINUS || et == NOT || et == LNOT) + { + eval_token et2 = eval_lex (v1); + if (et2 == ERROR) + return UNKNOWN_INPUT; + + if ((er = unary_term (et2, v1)) != NO_ERROR) + return er; + + /* Minimize undefined C behavior on overflow. This code assumes + that the implementation-defined overflow when casting + unsigned to signed is a silent twos-complement + wrap-around. */ + if (et == MINUS) + *v1 = (int32_t) -(uint32_t) *v1; + else if (et == NOT) + *v1 = ~*v1; + else if (et == LNOT) + *v1 = *v1 == 0 ? 1 : 0; + } + else if ((er = simple_term (et, v1)) != NO_ERROR) + return er; + + return NO_ERROR; +} + +static eval_error +simple_term (eval_token et, int32_t *v1) +{ + int32_t v2; + eval_error er; + + switch (et) + { + case LEFTP: + et = eval_lex (v1); + if (et == ERROR) + return UNKNOWN_INPUT; + + if ((er = logical_or_term (et, v1)) != NO_ERROR) + return er; + + et = eval_lex (&v2); + if (et == ERROR) + return UNKNOWN_INPUT; + + if (et != RIGHTP) + return MISSING_RIGHT; + + break; + + case NUMBER: + break; + + case BADOP: + return INVALID_OPERATOR; + + default: + return SYNTAX_ERROR; + } + return NO_ERROR; +} diff --git a/src/format.c b/src/format.c new file mode 100644 index 0000000..43a23cd --- /dev/null +++ b/src/format.c @@ -0,0 +1,385 @@ +/* GNU m4 -- A simple macro processor + + Copyright (C) 1989-1994, 2006-2011 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/>. +*/ + +/* printf like formatting for m4. */ + +#include "m4.h" +#include "xvasprintf.h" + +/* Simple varargs substitute. We assume int and unsigned int are the + same size; likewise for long and unsigned long. */ + +/* Parse STR as an integer, reporting warnings. */ +static int +arg_int (const char *str) +{ + char *endp; + long value; + size_t len = strlen (str); + + if (!len) + { + M4ERROR ((warning_status, 0, _("empty string treated as 0"))); + return 0; + } + errno = 0; + value = strtol (str, &endp, 10); + if (endp - str - len) + M4ERROR ((warning_status, 0, _("non-numeric argument %s"), str)); + else if (isspace (to_uchar (*str))) + M4ERROR ((warning_status, 0, _("leading whitespace ignored"))); + else if (errno == ERANGE || (int) value != value) + M4ERROR ((warning_status, 0, _("numeric overflow detected"))); + return value; +} + +/* Parse STR as a long, reporting warnings. */ +static long +arg_long (const char *str) +{ + char *endp; + long value; + size_t len = strlen (str); + + if (!len) + { + M4ERROR ((warning_status, 0, _("empty string treated as 0"))); + return 0L; + } + errno = 0; + value = strtol (str, &endp, 10); + if (endp - str - len) + M4ERROR ((warning_status, 0, _("non-numeric argument %s"), str)); + else if (isspace (to_uchar (*str))) + M4ERROR ((warning_status, 0, _("leading whitespace ignored"))); + else if (errno == ERANGE) + M4ERROR ((warning_status, 0, _("numeric overflow detected"))); + return value; +} + +/* Parse STR as a double, reporting warnings. */ +static double +arg_double (const char *str) +{ + char *endp; + double value; + size_t len = strlen (str); + + if (!len) + { + M4ERROR ((warning_status, 0, _("empty string treated as 0"))); + return 0.0; + } + errno = 0; + value = strtod (str, &endp); + if (endp - str - len) + M4ERROR ((warning_status, 0, _("non-numeric argument %s"), str)); + else if (isspace (to_uchar (*str))) + M4ERROR ((warning_status, 0, _("leading whitespace ignored"))); + else if (errno == ERANGE) + M4ERROR ((warning_status, 0, _("numeric overflow detected"))); + return value; +} + +#define ARG_INT(argc, argv) \ + ((argc == 0) ? 0 : \ + (--argc, argv++, arg_int (TOKEN_DATA_TEXT (argv[-1])))) + +#define ARG_LONG(argc, argv) \ + ((argc == 0) ? 0 : \ + (--argc, argv++, arg_long (TOKEN_DATA_TEXT (argv[-1])))) + +#define ARG_STR(argc, argv) \ + ((argc == 0) ? "" : \ + (--argc, argv++, TOKEN_DATA_TEXT (argv[-1]))) + +#define ARG_DOUBLE(argc, argv) \ + ((argc == 0) ? 0 : \ + (--argc, argv++, arg_double (TOKEN_DATA_TEXT (argv[-1])))) + + +/*------------------------------------------------------------------. +| The main formatting function. Output is placed on the obstack | +| OBS, the first argument in ARGV is the formatting string, and the | +| rest is arguments for the string. Warn rather than invoke | +| unspecified behavior in the underlying printf when we do not | +| recognize a format. | +`------------------------------------------------------------------*/ + +void +expand_format (struct obstack *obs, int argc, token_data **argv) +{ + const char *f; /* format control string */ + const char *fmt; /* position within f */ + char fstart[] = "%'+- 0#*.*hhd"; /* current format spec */ + char *p; /* position within fstart */ + unsigned char c; /* a simple character */ + + /* Flags. */ + char flags; /* flags to use in fstart */ + enum { + THOUSANDS = 0x01, /* ' */ + PLUS = 0x02, /* + */ + MINUS = 0x04, /* - */ + SPACE = 0x08, /* */ + ZERO = 0x10, /* 0 */ + ALT = 0x20, /* # */ + DONE = 0x40 /* no more flags */ + }; + + /* Precision specifiers. */ + int width; /* minimum field width */ + int prec; /* precision */ + char lflag; /* long flag */ + + /* Specifiers we are willing to accept. ok['x'] implies %x is ok. + Various modifiers reduce the set, in order to avoid undefined + behavior in printf. */ + char ok[128]; + + /* Buffer and stuff. */ + char *str; /* malloc'd buffer of formatted text */ + enum {CHAR, INT, LONG, DOUBLE, STR} datatype; + + f = fmt = ARG_STR (argc, argv); + memset (ok, 0, sizeof ok); + while (1) + { + const char *percent = strchr (fmt, '%'); + if (!percent) + { + obstack_grow (obs, fmt, strlen (fmt)); + return; + } + obstack_grow (obs, fmt, percent - fmt); + fmt = percent + 1; + + if (*fmt == '%') + { + obstack_1grow (obs, '%'); + fmt++; + continue; + } + + p = fstart + 1; /* % */ + lflag = 0; + ok['a'] = ok['A'] = ok['c'] = ok['d'] = ok['e'] = ok['E'] + = ok['f'] = ok['F'] = ok['g'] = ok['G'] = ok['i'] = ok['o'] + = ok['s'] = ok['u'] = ok['x'] = ok['X'] = 1; + + /* Parse flags. */ + flags = 0; + do + { + switch (*fmt) + { + case '\'': /* thousands separator */ + ok['a'] = ok['A'] = ok['c'] = ok['e'] = ok['E'] + = ok['o'] = ok['s'] = ok['x'] = ok['X'] = 0; + flags |= THOUSANDS; + break; + + case '+': /* mandatory sign */ + ok['c'] = ok['o'] = ok['s'] = ok['u'] = ok['x'] = ok['X'] = 0; + flags |= PLUS; + break; + + case ' ': /* space instead of positive sign */ + ok['c'] = ok['o'] = ok['s'] = ok['u'] = ok['x'] = ok['X'] = 0; + flags |= SPACE; + break; + + case '0': /* zero padding */ + ok['c'] = ok['s'] = 0; + flags |= ZERO; + break; + + case '#': /* alternate output */ + ok['c'] = ok['d'] = ok['i'] = ok['s'] = ok['u'] = 0; + flags |= ALT; + break; + + case '-': /* left justification */ + flags |= MINUS; + break; + + default: + flags |= DONE; + break; + } + } + while (!(flags & DONE) && fmt++); + if (flags & THOUSANDS) + *p++ = '\''; + if (flags & PLUS) + *p++ = '+'; + if (flags & MINUS) + *p++ = '-'; + if (flags & SPACE) + *p++ = ' '; + if (flags & ZERO) + *p++ = '0'; + if (flags & ALT) + *p++ = '#'; + + /* Minimum field width; an explicit 0 is the same as not giving + the width. */ + width = 0; + *p++ = '*'; + if (*fmt == '*') + { + width = ARG_INT (argc, argv); + fmt++; + } + else + while (isdigit (to_uchar (*fmt))) + { + width = 10 * width + *fmt - '0'; + fmt++; + } + + /* Maximum precision; an explicit negative precision is the same + as not giving the precision. A lone '.' is a precision of 0. */ + prec = -1; + *p++ = '.'; + *p++ = '*'; + if (*fmt == '.') + { + ok['c'] = 0; + if (*(++fmt) == '*') + { + prec = ARG_INT (argc, argv); + ++fmt; + } + else + { + prec = 0; + while (isdigit (to_uchar (*fmt))) + { + prec = 10 * prec + *fmt - '0'; + fmt++; + } + } + } + + /* Length modifiers. We don't yet recognize ll, j, t, or z. */ + if (*fmt == 'l') + { + *p++ = 'l'; + lflag = 1; + fmt++; + ok['c'] = ok['s'] = 0; + } + else if (*fmt == 'h') + { + *p++ = 'h'; + fmt++; + if (*fmt == 'h') + { + *p++ = 'h'; + fmt++; + } + ok['a'] = ok['A'] = ok['c'] = ok['e'] = ok['E'] = ok['f'] = ok['F'] + = ok['g'] = ok['G'] = ok['s'] = 0; + } + + c = *fmt++; + if (sizeof ok <= c || !ok[c]) + { + M4ERROR ((warning_status, 0, + "Warning: unrecognized specifier in `%s'", f)); + if (c == '\0') + fmt--; + continue; + } + + /* Specifiers. We don't yet recognize C, S, n, or p. */ + switch (c) + { + case 'c': + datatype = CHAR; + p -= 2; /* %.*c is undefined, so undo the '.*'. */ + break; + + case 's': + datatype = STR; + break; + + case 'd': + case 'i': + case 'o': + case 'x': + case 'X': + case 'u': + datatype = lflag ? LONG : INT; + break; + + case 'a': + case 'A': + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + datatype = DOUBLE; + break; + + default: + abort (); + } + *p++ = c; + *p = '\0'; + + switch (datatype) + { + case CHAR: + str = xasprintf (fstart, width, ARG_INT(argc, argv)); + break; + + case INT: + str = xasprintf (fstart, width, prec, ARG_INT(argc, argv)); + break; + + case LONG: + str = xasprintf (fstart, width, prec, ARG_LONG(argc, argv)); + break; + + case DOUBLE: + str = xasprintf (fstart, width, prec, ARG_DOUBLE(argc, argv)); + break; + + case STR: + str = xasprintf (fstart, width, prec, ARG_STR(argc, argv)); + break; + + default: + abort(); + } + + /* NULL was returned on failure, such as invalid format string. For + now, just silently ignore that bad specifier. */ + if (str == NULL) + continue; + + obstack_grow (obs, str, strlen (str)); + free (str); + } +} diff --git a/src/freeze.c b/src/freeze.c new file mode 100644 index 0000000..bf47376 --- /dev/null +++ b/src/freeze.c @@ -0,0 +1,398 @@ +/* GNU m4 -- A simple macro processor + + Copyright (C) 1989-1994, 2006-2011 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/>. +*/ + +/* This module handles frozen files. */ + +#include "m4.h" + +/*-------------------------------------------------------------------. +| Destructively reverse a symbol list and return the reversed list. | +`-------------------------------------------------------------------*/ + +static symbol * +reverse_symbol_list (symbol *sym) +{ + symbol *result; + symbol *next; + + result = NULL; + while (sym) + { + next = SYMBOL_NEXT (sym); + SYMBOL_NEXT (sym) = result; + result = sym; + sym = next; + } + return result; +} + +/*------------------------------------------------. +| Produce a frozen state to the given file NAME. | +`------------------------------------------------*/ + +void +produce_frozen_state (const char *name) +{ + FILE *file; + size_t h; + symbol *sym; + const builtin *bp; + + file = fopen (name, O_BINARY ? "wb" : "w"); + if (!file) + { + M4ERROR ((EXIT_FAILURE, errno, "cannot open `%s'", name)); + return; + } + + /* Write a recognizable header. */ + + xfprintf (file, "# This is a frozen state file generated by %s\n", + PACKAGE_STRING); + xfprintf (file, "V1\n"); + + /* Dump quote delimiters. */ + + if (strcmp (lquote.string, DEF_LQUOTE) || strcmp (rquote.string, DEF_RQUOTE)) + { + xfprintf (file, "Q%d,%d\n", (int) lquote.length, (int) rquote.length); + fputs (lquote.string, file); + fputs (rquote.string, file); + fputc ('\n', file); + } + + /* Dump comment delimiters. */ + + if (strcmp (bcomm.string, DEF_BCOMM) || strcmp (ecomm.string, DEF_ECOMM)) + { + xfprintf (file, "C%d,%d\n", (int) bcomm.length, (int) ecomm.length); + fputs (bcomm.string, file); + fputs (ecomm.string, file); + fputc ('\n', file); + } + + /* Dump all symbols. */ + + for (h = 0; h < hash_table_size; h++) + { + + /* Process all entries in one bucket, from the last to the first. + This order ensures that, at reload time, pushdef's will be + executed with the oldest definitions first. */ + + symtab[h] = reverse_symbol_list (symtab[h]); + for (sym = symtab[h]; sym; sym = SYMBOL_NEXT (sym)) + { + switch (SYMBOL_TYPE (sym)) + { + case TOKEN_TEXT: + xfprintf (file, "T%d,%d\n", + (int) strlen (SYMBOL_NAME (sym)), + (int) strlen (SYMBOL_TEXT (sym))); + fputs (SYMBOL_NAME (sym), file); + fputs (SYMBOL_TEXT (sym), file); + fputc ('\n', file); + break; + + case TOKEN_FUNC: + bp = find_builtin_by_addr (SYMBOL_FUNC (sym)); + if (bp == NULL) + { + M4ERROR ((warning_status, 0, "\ +INTERNAL ERROR: builtin not found in builtin table!")); + abort (); + } + xfprintf (file, "F%d,%d\n", + (int) strlen (SYMBOL_NAME (sym)), + (int) strlen (bp->name)); + fputs (SYMBOL_NAME (sym), file); + fputs (bp->name, file); + fputc ('\n', file); + break; + + case TOKEN_VOID: + /* Ignore placeholder tokens that exist due to traceon. */ + break; + + default: + M4ERROR ((warning_status, 0, "\ +INTERNAL ERROR: bad token data type in freeze_one_symbol ()")); + abort (); + break; + } + } + + /* Reverse the bucket once more, putting it back as it was. */ + + symtab[h] = reverse_symbol_list (symtab[h]); + } + + /* Let diversions be issued from output.c module, its cleaner to have this + piece of code there. */ + + freeze_diversions (file); + + /* All done. */ + + fputs ("# End of frozen state file\n", file); + if (close_stream (file) != 0) + M4ERROR ((EXIT_FAILURE, errno, "unable to create frozen state")); +} + +/*----------------------------------------------------------------------. +| Issue a message saying that some character is an EXPECTED character. | +`----------------------------------------------------------------------*/ + +static void +issue_expect_message (int expected) +{ + if (expected == '\n') + M4ERROR ((EXIT_FAILURE, 0, "expecting line feed in frozen file")); + else + M4ERROR ((EXIT_FAILURE, 0, "expecting character `%c' in frozen file", + expected)); +} + +/*-------------------------------------------------. +| Reload a frozen state from the given file NAME. | +`-------------------------------------------------*/ + +/* We are seeking speed, here. */ + +void +reload_frozen_state (const char *name) +{ + FILE *file; + int character; + int operation; + char *string[2]; + int allocated[2]; + int number[2]; + const builtin *bp; + bool advance_line = true; + +#define GET_CHARACTER \ + do \ + { \ + if (advance_line) \ + { \ + current_line++; \ + advance_line = false; \ + } \ + (character = getc (file)); \ + if (character == '\n') \ + advance_line = true; \ + } \ + while (0) + +#define GET_NUMBER(Number, AllowNeg) \ + do \ + { \ + unsigned int n = 0; \ + while (isdigit (character) && n <= INT_MAX / 10U) \ + { \ + n = 10 * n + character - '0'; \ + GET_CHARACTER; \ + } \ + if (((AllowNeg) ? INT_MIN : INT_MAX) + 0U < n \ + || isdigit (character)) \ + m4_error (EXIT_FAILURE, 0, \ + _("integer overflow in frozen file")); \ + (Number) = n; \ + } \ + while (0) + +#define VALIDATE(Expected) \ + do \ + { \ + if (character != (Expected)) \ + issue_expect_message (Expected); \ + } \ + while (0) + + /* Skip comments (`#' at beginning of line) and blank lines, setting + character to the next directive or to EOF. */ + +#define GET_DIRECTIVE \ + do \ + { \ + GET_CHARACTER; \ + if (character == '#') \ + { \ + while (character != EOF && character != '\n') \ + GET_CHARACTER; \ + VALIDATE ('\n'); \ + } \ + } \ + while (character == '\n') + +#define GET_STRING(i) \ + do \ + { \ + void *tmp; \ + char *p; \ + if (number[(i)] + 1 > allocated[(i)]) \ + { \ + free (string[(i)]); \ + allocated[(i)] = number[(i)] + 1; \ + string[(i)] = xcharalloc ((size_t) allocated[(i)]); \ + } \ + if (number[(i)] > 0 \ + && !fread (string[(i)], (size_t) number[(i)], 1, file)) \ + m4_error (EXIT_FAILURE, 0, \ + _("premature end of frozen file")); \ + string[(i)][number[(i)]] = '\0'; \ + p = string[(i)]; \ + while ((tmp = memchr(p, '\n', number[(i)] - (p - string[(i)])))) \ + { \ + current_line++; \ + p = (char *) tmp + 1; \ + } \ + } \ + while (0) + + file = m4_path_search (name, NULL); + if (file == NULL) + M4ERROR ((EXIT_FAILURE, errno, "cannot open %s", name)); + current_file = name; + + allocated[0] = 100; + string[0] = xcharalloc ((size_t) allocated[0]); + allocated[1] = 100; + string[1] = xcharalloc ((size_t) allocated[1]); + + /* Validate format version. Only `1' is acceptable for now. */ + GET_DIRECTIVE; + VALIDATE ('V'); + GET_CHARACTER; + GET_NUMBER (number[0], false); + if (number[0] > 1) + M4ERROR ((EXIT_MISMATCH, 0, + "frozen file version %d greater than max supported of 1", + number[0])); + else if (number[0] < 1) + M4ERROR ((EXIT_FAILURE, 0, + "ill-formed frozen file, version directive expected")); + VALIDATE ('\n'); + + GET_DIRECTIVE; + while (character != EOF) + { + switch (character) + { + default: + M4ERROR ((EXIT_FAILURE, 0, "ill-formed frozen file")); + + case 'C': + case 'D': + case 'F': + case 'T': + case 'Q': + operation = character; + GET_CHARACTER; + + /* Get string lengths. Accept a negative diversion number. */ + + if (operation == 'D' && character == '-') + { + GET_CHARACTER; + GET_NUMBER (number[0], true); + number[0] = -number[0]; + } + else + GET_NUMBER (number[0], false); + VALIDATE (','); + GET_CHARACTER; + GET_NUMBER (number[1], false); + VALIDATE ('\n'); + + if (operation != 'D') + GET_STRING (0); + GET_STRING (1); + GET_CHARACTER; + VALIDATE ('\n'); + + /* Act according to operation letter. */ + + switch (operation) + { + case 'C': + + /* Change comment strings. */ + + set_comment (string[0], string[1]); + break; + + case 'D': + + /* Select a diversion and add a string to it. */ + + make_diversion (number[0]); + if (number[1] > 0) + output_text (string[1], number[1]); + break; + + case 'F': + + /* Enter a macro having a builtin function as a definition. */ + + bp = find_builtin_by_name (string[1]); + define_builtin (string[0], bp, SYMBOL_PUSHDEF); + break; + + case 'T': + + /* Enter a macro having an expansion text as a definition. */ + + define_user_macro (string[0], string[1], SYMBOL_PUSHDEF); + break; + + case 'Q': + + /* Change quote strings. */ + + set_quotes (string[0], string[1]); + break; + + default: + + /* Cannot happen. */ + + break; + } + break; + + } + GET_DIRECTIVE; + } + + free (string[0]); + free (string[1]); + if (close_stream (file) != 0) + m4_error (EXIT_FAILURE, errno, _("unable to read frozen state")); + current_file = NULL; + current_line = 0; + +#undef GET_CHARACTER +#undef GET_DIRECTIVE +#undef GET_NUMBER +#undef VALIDATE +#undef GET_STRING +} diff --git a/src/input.c b/src/input.c new file mode 100644 index 0000000..be259a0 --- /dev/null +++ b/src/input.c @@ -0,0 +1,1156 @@ +/* GNU m4 -- A simple macro processor + + Copyright (C) 1989-1994, 2004-2011 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/>. +*/ + +/* Handling of different input sources, and lexical analysis. */ + +#include "m4.h" + +#include "memchr2.h" + +/* Unread input can be either files, that should be read (eg. included + files), strings, which should be rescanned (eg. macro expansion text), + or quoted macro definitions (as returned by the builtin "defn"). + Unread input are organised in a stack, implemented with an obstack. + Each input source is described by a "struct input_block". The obstack + is "current_input". The top of the input stack is "isp". + + The macro "m4wrap" places the text to be saved on another input + stack, on the obstack "wrapup_stack", whose top is "wsp". When EOF + is seen on normal input (eg, when "current_input" is empty), input is + switched over to "wrapup_stack", and the original "current_input" is + freed. A new stack is allocated for "wrapup_stack", which will + accept any text produced by calls to "m4wrap" from within the + wrapped text. This process of shuffling "wrapup_stack" to + "current_input" can continue indefinitely, even generating infinite + loops (e.g. "define(`f',`m4wrap(`f')')f"), without memory leaks. + + Pushing new input on the input stack is done by push_file (), + push_string (), push_wrapup () (for wrapup text), and push_macro () + (for macro definitions). Because macro expansion needs direct access + to the current input obstack (for optimisation), push_string () are + split in two functions, push_string_init (), which returns a pointer + to the current input stack, and push_string_finish (), which return a + pointer to the final text. The input_block *next is used to manage + the coordination between the different push routines. + + The current file and line number are stored in two global + variables, for use by the error handling functions in m4.c. Macro + expansion wants to report the line where a macro name was detected, + rather than where it finished collecting arguments. This also + applies to text resulting from macro expansions. So each input + block maintains its own notion of the current file and line, and + swapping between input blocks updates the global variables + accordingly. */ + +#ifdef ENABLE_CHANGEWORD +#include "regex.h" +#endif + +enum input_type +{ + INPUT_STRING, /* String resulting from macro expansion. */ + INPUT_FILE, /* File from command line or include. */ + INPUT_MACRO /* Builtin resulting from defn. */ +}; + +typedef enum input_type input_type; + +struct input_block +{ + struct input_block *prev; /* previous input_block on the input stack */ + input_type type; /* see enum values */ + const char *file; /* file where this input is from */ + int line; /* line where this input is from */ + union + { + struct + { + char *string; /* remaining string value */ + char *end; /* terminating NUL of string */ + } + u_s; /* INPUT_STRING */ + struct + { + FILE *fp; /* input file handle */ + bool_bitfield end : 1; /* true if peek has seen EOF */ + bool_bitfield close : 1; /* true if we should close file on pop */ + bool_bitfield advance : 1; /* track previous start_of_input_line */ + } + u_f; /* INPUT_FILE */ + builtin_func *func; /* pointer to macro's function */ + } + u; +}; + +typedef struct input_block input_block; + + +/* Current input file name. */ +const char *current_file; + +/* Current input line number. */ +int current_line; + +/* Obstack for storing individual tokens. */ +static struct obstack token_stack; + +/* Obstack for storing file names. */ +static struct obstack file_names; + +/* Wrapup input stack. */ +static struct obstack *wrapup_stack; + +/* Current stack, from input or wrapup. */ +static struct obstack *current_input; + +/* Bottom of token_stack, for obstack_free. */ +static void *token_bottom; + +/* Pointer to top of current_input. */ +static input_block *isp; + +/* Pointer to top of wrapup_stack. */ +static input_block *wsp; + +/* Aux. for handling split push_string (). */ +static input_block *next; + +/* Flag for next_char () to increment current_line. */ +static bool start_of_input_line; + +/* Flag for next_char () to recognize change in input block. */ +static bool input_change; + +#define CHAR_EOF 256 /* character return on EOF */ +#define CHAR_MACRO 257 /* character return for MACRO token */ + +/* Quote chars. */ +STRING rquote; +STRING lquote; + +/* Comment chars. */ +STRING bcomm; +STRING ecomm; + +#ifdef ENABLE_CHANGEWORD + +# define DEFAULT_WORD_REGEXP "[_a-zA-Z][_a-zA-Z0-9]*" + +static struct re_pattern_buffer word_regexp; +static int default_word_regexp; +static struct re_registers regs; + +#else /* ! ENABLE_CHANGEWORD */ +# define default_word_regexp 1 +#endif /* ! ENABLE_CHANGEWORD */ + +#ifdef DEBUG_INPUT +static const char *token_type_string (token_type); +#endif + + +/*-------------------------------------------------------------------. +| push_file () pushes an input file on the input stack, saving the | +| current file name and line number. If next is non-NULL, this push | +| invalidates a call to push_string_init (), whose storage is | +| consequently released. If CLOSE_WHEN_DONE, then close FP after | +| EOF is detected. | +`-------------------------------------------------------------------*/ + +void +push_file (FILE *fp, const char *title, bool close_when_done) +{ + input_block *i; + + if (next != NULL) + { + obstack_free (current_input, next); + next = NULL; + } + + if (debug_level & DEBUG_TRACE_INPUT) + DEBUG_MESSAGE1 ("input read from %s", title); + + i = (input_block *) obstack_alloc (current_input, + sizeof (struct input_block)); + i->type = INPUT_FILE; + i->file = (char *) obstack_copy0 (&file_names, title, strlen (title)); + i->line = 1; + input_change = true; + + i->u.u_f.fp = fp; + i->u.u_f.end = false; + i->u.u_f.close = close_when_done; + i->u.u_f.advance = start_of_input_line; + output_current_line = -1; + + i->prev = isp; + isp = i; +} + +/*---------------------------------------------------------------. +| push_macro () pushes a builtin macro's definition on the input | +| stack. If next is non-NULL, this push invalidates a call to | +| push_string_init (), whose storage is consequently released. | +`---------------------------------------------------------------*/ + +void +push_macro (builtin_func *func) +{ + input_block *i; + + if (next != NULL) + { + obstack_free (current_input, next); + next = NULL; + } + + i = (input_block *) obstack_alloc (current_input, + sizeof (struct input_block)); + i->type = INPUT_MACRO; + i->file = current_file; + i->line = current_line; + input_change = true; + + i->u.func = func; + i->prev = isp; + isp = i; +} + +/*------------------------------------------------------------------. +| First half of push_string (). The pointer next points to the new | +| input_block. | +`------------------------------------------------------------------*/ + +struct obstack * +push_string_init (void) +{ + if (next != NULL) + { + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: recursive push_string!")); + abort (); + } + + next = (input_block *) obstack_alloc (current_input, + sizeof (struct input_block)); + next->type = INPUT_STRING; + next->file = current_file; + next->line = current_line; + + return current_input; +} + +/*-------------------------------------------------------------------. +| Last half of push_string (). If next is now NULL, a call to | +| push_file () has invalidated the previous call to push_string_init | +| (), so we just give up. If the new object is void, we do not push | +| it. The function push_string_finish () returns a pointer to the | +| finished object. This pointer is only for temporary use, since | +| reading the next token might release the memory used for the | +| object. | +`-------------------------------------------------------------------*/ + +const char * +push_string_finish (void) +{ + const char *ret = NULL; + + if (next == NULL) + return NULL; + + if (obstack_object_size (current_input) > 0) + { + size_t len = obstack_object_size (current_input); + obstack_1grow (current_input, '\0'); + next->u.u_s.string = (char *) obstack_finish (current_input); + next->u.u_s.end = next->u.u_s.string + len; + next->prev = isp; + isp = next; + ret = isp->u.u_s.string; /* for immediate use only */ + input_change = true; + } + else + obstack_free (current_input, next); /* people might leave garbage on it. */ + next = NULL; + return ret; +} + +/*------------------------------------------------------------------. +| The function push_wrapup () pushes a string on the wrapup stack. | +| When the normal input stack gets empty, the wrapup stack will | +| become the input stack, and push_string () and push_file () will | +| operate on wrapup_stack. Push_wrapup should be done as | +| push_string (), but this will suffice, as long as arguments to | +| m4_m4wrap () are moderate in size. | +`------------------------------------------------------------------*/ + +void +push_wrapup (const char *s) +{ + size_t len = strlen (s); + input_block *i; + i = (input_block *) obstack_alloc (wrapup_stack, + sizeof (struct input_block)); + i->prev = wsp; + i->type = INPUT_STRING; + i->file = current_file; + i->line = current_line; + i->u.u_s.string = (char *) obstack_copy0 (wrapup_stack, s, len); + i->u.u_s.end = i->u.u_s.string + len; + wsp = i; +} + + +/*-------------------------------------------------------------------. +| The function pop_input () pops one level of input sources. If the | +| popped input_block is a file, current_file and current_line are | +| reset to the saved values before the memory for the input_block is | +| released. | +`-------------------------------------------------------------------*/ + +static void +pop_input (void) +{ + input_block *tmp = isp->prev; + + switch (isp->type) + { + case INPUT_STRING: + case INPUT_MACRO: + break; + + case INPUT_FILE: + if (debug_level & DEBUG_TRACE_INPUT) + { + if (tmp) + DEBUG_MESSAGE2 ("input reverted to %s, line %d", + tmp->file, tmp->line); + else + DEBUG_MESSAGE ("input exhausted"); + } + + if (ferror (isp->u.u_f.fp)) + { + M4ERROR ((warning_status, 0, "read error")); + if (isp->u.u_f.close) + fclose (isp->u.u_f.fp); + retcode = EXIT_FAILURE; + } + else if (isp->u.u_f.close && fclose (isp->u.u_f.fp) == EOF) + { + M4ERROR ((warning_status, errno, "error reading file")); + retcode = EXIT_FAILURE; + } + start_of_input_line = isp->u.u_f.advance; + output_current_line = -1; + break; + + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: input stack botch in pop_input ()")); + abort (); + } + obstack_free (current_input, isp); + next = NULL; /* might be set in push_string_init () */ + + isp = tmp; + input_change = true; +} + +/*-------------------------------------------------------------------. +| To switch input over to the wrapup stack, main calls pop_wrapup | +| (). Since wrapup text can install new wrapup text, pop_wrapup () | +| returns false when there is no wrapup text on the stack, and true | +| otherwise. | +`-------------------------------------------------------------------*/ + +bool +pop_wrapup (void) +{ + next = NULL; + obstack_free (current_input, NULL); + free (current_input); + + if (wsp == NULL) + { + /* End of the program. Free all memory even though we are about + to exit, since it makes leak detection easier. */ + obstack_free (&token_stack, NULL); + obstack_free (&file_names, NULL); + obstack_free (wrapup_stack, NULL); + free (wrapup_stack); +#ifdef ENABLE_CHANGEWORD + regfree (&word_regexp); +#endif /* ENABLE_CHANGEWORD */ + return false; + } + + current_input = wrapup_stack; + wrapup_stack = (struct obstack *) xmalloc (sizeof (struct obstack)); + obstack_init (wrapup_stack); + + isp = wsp; + wsp = NULL; + input_change = true; + + return true; +} + +/*-------------------------------------------------------------------. +| When a MACRO token is seen, next_token () uses init_macro_token () | +| to retrieve the value of the function pointer. | +`-------------------------------------------------------------------*/ + +static void +init_macro_token (token_data *td) +{ + if (isp->type != INPUT_MACRO) + { + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: bad call to init_macro_token ()")); + abort (); + } + + TOKEN_DATA_TYPE (td) = TOKEN_FUNC; + TOKEN_DATA_FUNC (td) = isp->u.func; +} + + +/*-----------------------------------------------------------------. +| Low level input is done a character at a time. The function | +| peek_input () is used to look at the next character in the input | +| stream. At any given time, it reads from the input_block on the | +| top of the current input stack. | +`-----------------------------------------------------------------*/ + +static int +peek_input (void) +{ + int ch; + input_block *block = isp; + + while (1) + { + if (block == NULL) + return CHAR_EOF; + + switch (block->type) + { + case INPUT_STRING: + ch = to_uchar (block->u.u_s.string[0]); + if (ch != '\0') + return ch; + break; + + case INPUT_FILE: + ch = getc (block->u.u_f.fp); + if (ch != EOF) + { + ungetc (ch, block->u.u_f.fp); + return ch; + } + block->u.u_f.end = true; + break; + + case INPUT_MACRO: + return CHAR_MACRO; + + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: input stack botch in peek_input ()")); + abort (); + } + block = block->prev; + } +} + +/*-------------------------------------------------------------------. +| The function next_char () is used to read and advance the input to | +| the next character. It also manages line numbers for error | +| messages, so they do not get wrong, due to lookahead. The token | +| consisting of a newline alone is taken as belonging to the line it | +| ends, and the current line number is not incremented until the | +| next character is read. 99.9% of all calls will read from a | +| string, so factor that out into a macro for speed. | +`-------------------------------------------------------------------*/ + +#define next_char() \ + (isp && isp->type == INPUT_STRING && isp->u.u_s.string[0] \ + && !input_change \ + ? to_uchar (*isp->u.u_s.string++) \ + : next_char_1 ()) + +static int +next_char_1 (void) +{ + int ch; + + while (1) + { + if (isp == NULL) + { + current_file = ""; + current_line = 0; + return CHAR_EOF; + } + + if (input_change) + { + current_file = isp->file; + current_line = isp->line; + input_change = false; + } + + switch (isp->type) + { + case INPUT_STRING: + ch = to_uchar (*isp->u.u_s.string++); + if (ch != '\0') + return ch; + break; + + case INPUT_FILE: + if (start_of_input_line) + { + start_of_input_line = false; + current_line = ++isp->line; + } + + /* If stdin is a terminal, calling getc after peek_input + already called it would make the user have to hit ^D + twice to quit. */ + ch = isp->u.u_f.end ? EOF : getc (isp->u.u_f.fp); + if (ch != EOF) + { + if (ch == '\n') + start_of_input_line = true; + return ch; + } + break; + + case INPUT_MACRO: + pop_input (); /* INPUT_MACRO input sources has only one token */ + return CHAR_MACRO; + + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: input stack botch in next_char ()")); + abort (); + } + + /* End of input source --- pop one level. */ + pop_input (); + } +} + +/*-------------------------------------------------------------------. +| skip_line () simply discards all immediately following characters, | +| upto the first newline. It is only used from m4_dnl (). | +`-------------------------------------------------------------------*/ + +void +skip_line (void) +{ + int ch; + const char *file = current_file; + int line = current_line; + + while ((ch = next_char ()) != CHAR_EOF && ch != '\n') + ; + if (ch == CHAR_EOF) + /* current_file changed to "" if we see CHAR_EOF, use the + previous value we stored earlier. */ + M4ERROR_AT_LINE ((warning_status, 0, file, line, + "Warning: end of file treated as newline")); + /* On the rare occasion that dnl crosses include file boundaries + (either the input file did not end in a newline, or changeword + was used), calling next_char can update current_file and + current_line, and that update will be undone as we return to + expand_macro. This informs next_char to fix things again. */ + if (file != current_file || line != current_line) + input_change = true; +} + + +/*------------------------------------------------------------------. +| This function is for matching a string against a prefix of the | +| input stream. If the string matches the input and consume is | +| true, the input is discarded; otherwise any characters read are | +| pushed back again. The function is used only when multicharacter | +| quotes or comment delimiters are used. | +`------------------------------------------------------------------*/ + +static bool +match_input (const char *s, bool consume) +{ + int n; /* number of characters matched */ + int ch; /* input character */ + const char *t; + bool result = false; + + ch = peek_input (); + if (ch != to_uchar (*s)) + return false; /* fail */ + + if (s[1] == '\0') + { + if (consume) + next_char (); + return true; /* short match */ + } + + next_char (); + for (n = 1, t = s++; peek_input () == to_uchar (*s++); ) + { + next_char (); + n++; + if (*s == '\0') /* long match */ + { + if (consume) + return true; + result = true; + break; + } + } + + /* Failed or shouldn't consume, push back input. */ + { + struct obstack *h = push_string_init (); + + /* `obstack_grow' may be macro evaluating its arg 1 several times. */ + obstack_grow (h, t, n); + } + push_string_finish (); + return result; +} + +/*--------------------------------------------------------------------. +| The macro MATCH() is used to match a string S against the input. | +| The first character is handled inline, for speed. Hopefully, this | +| will not hurt efficiency too much when single character quotes and | +| comment delimiters are used. If CONSUME, then CH is the result of | +| next_char, and a successful match will discard the matched string. | +| Otherwise, CH is the result of peek_char, and the input stream is | +| effectively unchanged. | +`--------------------------------------------------------------------*/ + +#define MATCH(ch, s, consume) \ + (to_uchar ((s)[0]) == (ch) \ + && (ch) != '\0' \ + && ((s)[1] == '\0' || (match_input ((s) + (consume), consume)))) + + +/*--------------------------------------------------------. +| Initialize input stacks, and quote/comment characters. | +`--------------------------------------------------------*/ + +void +input_init (void) +{ + current_file = ""; + current_line = 0; + + current_input = (struct obstack *) xmalloc (sizeof (struct obstack)); + obstack_init (current_input); + wrapup_stack = (struct obstack *) xmalloc (sizeof (struct obstack)); + obstack_init (wrapup_stack); + + obstack_init (&file_names); + + /* Allocate an object in the current chunk, so that obstack_free + will always work even if the first token parsed spills to a new + chunk. */ + obstack_init (&token_stack); + obstack_alloc (&token_stack, 1); + token_bottom = obstack_base (&token_stack); + + isp = NULL; + wsp = NULL; + next = NULL; + + start_of_input_line = false; + + lquote.string = xstrdup (DEF_LQUOTE); + lquote.length = strlen (lquote.string); + rquote.string = xstrdup (DEF_RQUOTE); + rquote.length = strlen (rquote.string); + bcomm.string = xstrdup (DEF_BCOMM); + bcomm.length = strlen (bcomm.string); + ecomm.string = xstrdup (DEF_ECOMM); + ecomm.length = strlen (ecomm.string); + +#ifdef ENABLE_CHANGEWORD + set_word_regexp (user_word_regexp); +#endif +} + + +/*------------------------------------------------------------------. +| Functions for setting quotes and comment delimiters. Used by | +| m4_changecom () and m4_changequote (). Pass NULL if the argument | +| was not present, to distinguish from an explicit empty string. | +`------------------------------------------------------------------*/ + +void +set_quotes (const char *lq, const char *rq) +{ + free (lquote.string); + free (rquote.string); + + /* POSIX states that with 0 arguments, the default quotes are used. + POSIX XCU ERN 112 states that behavior is implementation-defined + if there was only one argument, or if there is an empty string in + either position when there are two arguments. We allow an empty + left quote to disable quoting, but a non-empty left quote will + always create a non-empty right quote. See the texinfo for what + some other implementations do. */ + if (!lq) + { + lq = DEF_LQUOTE; + rq = DEF_RQUOTE; + } + else if (!rq || (*lq && !*rq)) + rq = DEF_RQUOTE; + + lquote.string = xstrdup (lq); + lquote.length = strlen (lquote.string); + rquote.string = xstrdup (rq); + rquote.length = strlen (rquote.string); +} + +void +set_comment (const char *bc, const char *ec) +{ + free (bcomm.string); + free (ecomm.string); + + /* POSIX requires no arguments to disable comments. It requires + empty arguments to be used as-is, but this is counter to + traditional behavior, because a non-null begin and null end makes + it impossible to end a comment. An aardvark has been filed: + http://www.opengroup.org/austin/mailarchives/ag-review/msg02168.html + This implementation assumes the aardvark will be approved. See + the texinfo for what some other implementations do. */ + if (!bc) + bc = ec = ""; + else if (!ec || (*bc && !*ec)) + ec = DEF_ECOMM; + + bcomm.string = xstrdup (bc); + bcomm.length = strlen (bcomm.string); + ecomm.string = xstrdup (ec); + ecomm.length = strlen (ecomm.string); +} + +#ifdef ENABLE_CHANGEWORD + +void +set_word_regexp (const char *regexp) +{ + const char *msg; + struct re_pattern_buffer new_word_regexp; + + if (!*regexp || STREQ (regexp, DEFAULT_WORD_REGEXP)) + { + default_word_regexp = true; + return; + } + + /* Dry run to see whether the new expression is compilable. */ + init_pattern_buffer (&new_word_regexp, NULL); + msg = re_compile_pattern (regexp, strlen (regexp), &new_word_regexp); + regfree (&new_word_regexp); + + if (msg != NULL) + { + M4ERROR ((warning_status, 0, + "bad regular expression `%s': %s", regexp, msg)); + return; + } + + /* If compilation worked, retry using the word_regexp struct. We + can't rely on struct assigns working, so redo the compilation. + The fastmap can be reused between compilations, and will be freed + by the final regfree. */ + if (!word_regexp.fastmap) + word_regexp.fastmap = xcharalloc (UCHAR_MAX + 1); + msg = re_compile_pattern (regexp, strlen (regexp), &word_regexp); + assert (!msg); + re_set_registers (&word_regexp, ®s, regs.num_regs, regs.start, regs.end); + if (re_compile_fastmap (&word_regexp)) + assert (false); + + default_word_regexp = false; +} + +#endif /* ENABLE_CHANGEWORD */ + + +/*--------------------------------------------------------------------. +| Parse and return a single token from the input stream. A token | +| can either be TOKEN_EOF, if the input_stack is empty; it can be | +| TOKEN_STRING for a quoted string; TOKEN_WORD for something that is | +| a potential macro name; and TOKEN_SIMPLE for any single character | +| that is not a part of any of the previous types. If LINE is not | +| NULL, set *LINE to the line where the token starts. | +| | +| Next_token () return the token type, and passes back a pointer to | +| the token data through TD. The token text is collected on the | +| obstack token_stack, which never contains more than one token text | +| at a time. The storage pointed to by the fields in TD is | +| therefore subject to change the next time next_token () is called. | +`--------------------------------------------------------------------*/ + +token_type +next_token (token_data *td, int *line) +{ + int ch; + int quote_level; + token_type type; +#ifdef ENABLE_CHANGEWORD + int startpos; + char *orig_text = NULL; +#endif + const char *file; + int dummy; + + obstack_free (&token_stack, token_bottom); + if (!line) + line = &dummy; + + /* Can't consume character until after CHAR_MACRO is handled. */ + ch = peek_input (); + if (ch == CHAR_EOF) + { +#ifdef DEBUG_INPUT + xfprintf (stderr, "next_token -> EOF\n"); +#endif + next_char (); + return TOKEN_EOF; + } + if (ch == CHAR_MACRO) + { + init_macro_token (td); + next_char (); +#ifdef DEBUG_INPUT + xfprintf (stderr, "next_token -> MACDEF (%s)\n", + find_builtin_by_addr (TOKEN_DATA_FUNC (td))->name); +#endif + return TOKEN_MACDEF; + } + + next_char (); /* Consume character we already peeked at. */ + file = current_file; + *line = current_line; + if (MATCH (ch, bcomm.string, true)) + { + obstack_grow (&token_stack, bcomm.string, bcomm.length); + while ((ch = next_char ()) != CHAR_EOF + && !MATCH (ch, ecomm.string, true)) + obstack_1grow (&token_stack, ch); + if (ch != CHAR_EOF) + obstack_grow (&token_stack, ecomm.string, ecomm.length); + else + /* current_file changed to "" if we see CHAR_EOF, use the + previous value we stored earlier. */ + M4ERROR_AT_LINE ((EXIT_FAILURE, 0, file, *line, + "ERROR: end of file in comment")); + + type = TOKEN_STRING; + } + else if (default_word_regexp && (isalpha (ch) || ch == '_')) + { + obstack_1grow (&token_stack, ch); + while ((ch = peek_input ()) != CHAR_EOF && (isalnum (ch) || ch == '_')) + { + obstack_1grow (&token_stack, ch); + next_char (); + } + type = TOKEN_WORD; + } + +#ifdef ENABLE_CHANGEWORD + + else if (!default_word_regexp && word_regexp.fastmap[ch]) + { + obstack_1grow (&token_stack, ch); + while (1) + { + ch = peek_input (); + if (ch == CHAR_EOF) + break; + obstack_1grow (&token_stack, ch); + startpos = re_search (&word_regexp, + (char *) obstack_base (&token_stack), + obstack_object_size (&token_stack), 0, 0, + ®s); + if (startpos || + regs.end [0] != (regoff_t) obstack_object_size (&token_stack)) + { + *(((char *) obstack_base (&token_stack) + + obstack_object_size (&token_stack)) - 1) = '\0'; + break; + } + next_char (); + } + + obstack_1grow (&token_stack, '\0'); + orig_text = (char *) obstack_finish (&token_stack); + + if (regs.start[1] != -1) + obstack_grow (&token_stack,orig_text + regs.start[1], + regs.end[1] - regs.start[1]); + else + obstack_grow (&token_stack, orig_text,regs.end[0]); + + type = TOKEN_WORD; + } + +#endif /* ENABLE_CHANGEWORD */ + + else if (!MATCH (ch, lquote.string, true)) + { + switch (ch) + { + case '(': + type = TOKEN_OPEN; + break; + case ',': + type = TOKEN_COMMA; + break; + case ')': + type = TOKEN_CLOSE; + break; + default: + type = TOKEN_SIMPLE; + break; + } + obstack_1grow (&token_stack, ch); + } + else + { + bool fast = lquote.length == 1 && rquote.length == 1; + quote_level = 1; + while (1) + { + /* Try scanning a buffer first. */ + const char *buffer = (isp && isp->type == INPUT_STRING + ? isp->u.u_s.string : NULL); + if (buffer && *buffer) + { + size_t len = isp->u.u_s.end - buffer; + const char *p = buffer; + do + { + p = (char *) memchr2 (p, *lquote.string, *rquote.string, + buffer + len - p); + } + while (p && fast && (*p++ == *rquote.string + ? --quote_level : ++quote_level)); + if (p) + { + if (fast) + { + assert (!quote_level); + obstack_grow (&token_stack, buffer, p - buffer - 1); + isp->u.u_s.string += p - buffer; + break; + } + obstack_grow (&token_stack, buffer, p - buffer); + ch = to_uchar (*p); + isp->u.u_s.string += p - buffer + 1; + } + else + { + obstack_grow (&token_stack, buffer, len); + isp->u.u_s.string += len; + continue; + } + } + /* Fall back to a byte. */ + else + ch = next_char (); + if (ch == CHAR_EOF) + /* current_file changed to "" if we see CHAR_EOF, use + the previous value we stored earlier. */ + M4ERROR_AT_LINE ((EXIT_FAILURE, 0, file, *line, + "ERROR: end of file in string")); + + if (MATCH (ch, rquote.string, true)) + { + if (--quote_level == 0) + break; + obstack_grow (&token_stack, rquote.string, rquote.length); + } + else if (MATCH (ch, lquote.string, true)) + { + quote_level++; + obstack_grow (&token_stack, lquote.string, lquote.length); + } + else + obstack_1grow (&token_stack, ch); + } + type = TOKEN_STRING; + } + + obstack_1grow (&token_stack, '\0'); + + TOKEN_DATA_TYPE (td) = TOKEN_TEXT; + TOKEN_DATA_TEXT (td) = (char *) obstack_finish (&token_stack); +#ifdef ENABLE_CHANGEWORD + if (orig_text == NULL) + orig_text = TOKEN_DATA_TEXT (td); + TOKEN_DATA_ORIG_TEXT (td) = orig_text; +#endif +#ifdef DEBUG_INPUT + xfprintf (stderr, "next_token -> %s (%s)\n", + token_type_string (type), TOKEN_DATA_TEXT (td)); +#endif + return type; +} + +/*-----------------------------------------------. +| Peek at the next token from the input stream. | +`-----------------------------------------------*/ + +token_type +peek_token (void) +{ + token_type result; + int ch = peek_input (); + + if (ch == CHAR_EOF) + { + result = TOKEN_EOF; + } + else if (ch == CHAR_MACRO) + { + result = TOKEN_MACDEF; + } + else if (MATCH (ch, bcomm.string, false)) + { + result = TOKEN_STRING; + } + else if ((default_word_regexp && (isalpha (ch) || ch == '_')) +#ifdef ENABLE_CHANGEWORD + || (! default_word_regexp && word_regexp.fastmap[ch]) +#endif /* ENABLE_CHANGEWORD */ + ) + { + result = TOKEN_WORD; + } + else if (MATCH (ch, lquote.string, false)) + { + result = TOKEN_STRING; + } + else + switch (ch) + { + case '(': + result = TOKEN_OPEN; + break; + case ',': + result = TOKEN_COMMA; + break; + case ')': + result = TOKEN_CLOSE; + break; + default: + result = TOKEN_SIMPLE; + } + +#ifdef DEBUG_INPUT + xfprintf (stderr, "peek_token -> %s\n", token_type_string (result)); +#endif /* DEBUG_INPUT */ + return result; +} + + +#ifdef DEBUG_INPUT + +static const char * +token_type_string (token_type t) +{ + switch (t) + { /* TOKSW */ + case TOKEN_EOF: + return "EOF"; + case TOKEN_STRING: + return "STRING"; + case TOKEN_WORD: + return "WORD"; + case TOKEN_OPEN: + return "OPEN"; + case TOKEN_COMMA: + return "COMMA"; + case TOKEN_CLOSE: + return "CLOSE"; + case TOKEN_SIMPLE: + return "SIMPLE"; + case TOKEN_MACDEF: + return "MACDEF"; + default: + abort (); + } + } + +static void +print_token (const char *s, token_type t, token_data *td) +{ + xfprintf (stderr, "%s: ", s); + switch (t) + { /* TOKSW */ + case TOKEN_OPEN: + case TOKEN_COMMA: + case TOKEN_CLOSE: + case TOKEN_SIMPLE: + xfprintf (stderr, "char:"); + break; + + case TOKEN_WORD: + xfprintf (stderr, "word:"); + break; + + case TOKEN_STRING: + xfprintf (stderr, "string:"); + break; + + case TOKEN_MACDEF: + xfprintf (stderr, "macro: %p\n", TOKEN_DATA_FUNC (td)); + break; + + case TOKEN_EOF: + xfprintf (stderr, "eof\n"); + break; + } + xfprintf (stderr, "\t\"%s\"\n", TOKEN_DATA_TEXT (td)); +} + +static void M4_GNUC_UNUSED +lex_debug (void) +{ + token_type t; + token_data td; + + while ((t = next_token (&td, NULL)) != TOKEN_EOF) + print_token ("lex", t, &td); +} +#endif /* DEBUG_INPUT */ diff --git a/src/m4.c b/src/m4.c new file mode 100644 index 0000000..d7ad7cd --- /dev/null +++ b/src/m4.c @@ -0,0 +1,688 @@ +/* GNU m4 -- A simple macro processor + + Copyright (C) 1989-1994, 2004-2011 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 "m4.h" + +#include <getopt.h> +#include <limits.h> +#include <signal.h> + +#include "c-stack.h" +#include "ignore-value.h" +#include "progname.h" +#include "version-etc.h" + +#ifdef DEBUG_STKOVF +# include "assert.h" +#endif + +#define AUTHORS "Rene' Seindal" + +static void usage (int) M4_GNUC_NORETURN; + +/* Enable sync output for /lib/cpp (-s). */ +int sync_output = 0; + +/* Debug (-d[flags]). */ +int debug_level = 0; + +/* Hash table size (should be a prime) (-Hsize). */ +size_t hash_table_size = HASHMAX; + +/* Disable GNU extensions (-G). */ +int no_gnu_extensions = 0; + +/* Prefix all builtin functions by `m4_'. */ +int prefix_all_builtins = 0; + +/* Max length of arguments in trace output (-lsize). */ +int max_debug_argument_length = 0; + +/* Suppress warnings about missing arguments. */ +int suppress_warnings = 0; + +/* If true, then warnings affect exit status. */ +static bool fatal_warnings = false; + +/* If not zero, then value of exit status for warning diagnostics. */ +int warning_status = 0; + +/* Artificial limit for expansion_level in macro.c. */ +int nesting_limit = 1024; + +#ifdef ENABLE_CHANGEWORD +/* User provided regexp for describing m4 words. */ +const char *user_word_regexp = ""; +#endif + +/* Global catchall for any errors that should affect final error status, but + where we try to continue execution in the meantime. */ +int retcode; + +struct macro_definition +{ + struct macro_definition *next; + int code; /* D, U, s, t, '\1', or DEBUGFILE_OPTION. */ + const char *arg; +}; +typedef struct macro_definition macro_definition; + +/* Error handling functions. */ + +/*-----------------------. +| Wrapper around error. | +`-----------------------*/ + +void +m4_error (int status, int errnum, const char *format, ...) +{ + va_list args; + va_start (args, format); + verror_at_line (status, errnum, current_line ? current_file : NULL, + current_line, format, args); + if (fatal_warnings && ! retcode) + retcode = EXIT_FAILURE; +} + +/*-------------------------------. +| Wrapper around error_at_line. | +`-------------------------------*/ + +void +m4_error_at_line (int status, int errnum, const char *file, int line, + const char *format, ...) +{ + va_list args; + va_start (args, format); + verror_at_line (status, errnum, line ? file : NULL, line, format, args); + if (fatal_warnings && ! retcode) + retcode = EXIT_FAILURE; +} + +#ifndef SIGBUS +# define SIGBUS SIGILL +#endif + +#ifndef NSIG +# ifndef MAX +# define MAX(a,b) ((a) < (b) ? (b) : (a)) +# endif +# define NSIG (MAX (SIGABRT, MAX (SIGILL, MAX (SIGFPE, \ + MAX (SIGSEGV, SIGBUS)))) + 1) +#endif + +/* Pre-translated messages for program errors. Do not translate in + the signal handler, since gettext and strsignal are not + async-signal-safe. */ +static const char * volatile program_error_message; +static const char * volatile signal_message[NSIG]; + +/* Print a nicer message about any programmer errors, then exit. This + must be aysnc-signal safe, since it is executed as a signal + handler. If SIGNO is zero, this represents a stack overflow; in + that case, we return to allow c_stack_action to handle things. */ +static void +fault_handler (int signo) +{ + if (signo) + { + /* POSIX states that reading static memory is, in general, not + async-safe. However, the static variables that we read are + never modified once this handler is installed, so this + particular usage is safe. And it seems an oversight that + POSIX claims strlen is not async-safe. Ignore write + failures, since we will exit with non-zero status anyway. */ +#define WRITE(f, b, l) ignore_value (write (f, b, l)) + WRITE (STDERR_FILENO, program_name, strlen (program_name)); + WRITE (STDERR_FILENO, ": ", 2); + WRITE (STDERR_FILENO, program_error_message, + strlen (program_error_message)); + if (signal_message[signo]) + { + WRITE (STDERR_FILENO, ": ", 2); + WRITE (STDERR_FILENO, signal_message[signo], + strlen (signal_message[signo])); + } + WRITE (STDERR_FILENO, "\n", 1); +#undef WRITE + _exit (EXIT_INTERNAL_ERROR); + } +} + + +/*---------------------------------------------. +| Print a usage message and exit with STATUS. | +`---------------------------------------------*/ + +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + xfprintf (stderr, "Try `%s --help' for more information.\n", program_name); + else + { + xprintf ("Usage: %s [OPTION]... [FILE]...\n", program_name); + fputs ("\ +Process macros in FILEs. If no FILE or if FILE is `-', standard input\n\ +is read.\n\ +", stdout); + fputs ("\ +\n\ +Mandatory or optional arguments to long options are mandatory or optional\n\ +for short options too.\n\ +\n\ +Operation modes:\n\ + --help display this help and exit\n\ + --version output version information and exit\n\ +", stdout); + xprintf ("\ + -E, --fatal-warnings once: warnings become errors, twice: stop\n\ + execution at first error\n\ + -i, --interactive unbuffer output, ignore interrupts\n\ + -P, --prefix-builtins force a `m4_' prefix to all builtins\n\ + -Q, --quiet, --silent suppress some warnings for builtins\n\ + --warn-macro-sequence[=REGEXP]\n\ + warn if macro definition matches REGEXP,\n\ + default %s\n\ +", DEFAULT_MACRO_SEQUENCE); +#ifdef ENABLE_CHANGEWORD + fputs ("\ + -W, --word-regexp=REGEXP use REGEXP for macro name syntax\n\ +", stdout); +#endif + fputs ("\ +\n\ +Preprocessor features:\n\ + -D, --define=NAME[=VALUE] define NAME as having VALUE, or empty\n\ + -I, --include=DIRECTORY append DIRECTORY to include path\n\ + -s, --synclines generate `#line NUM \"FILE\"' lines\n\ + -U, --undefine=NAME undefine NAME\n\ +", stdout); + puts (""); + xprintf (_("\ +Limits control:\n\ + -g, --gnu override -G to re-enable GNU extensions\n\ + -G, --traditional suppress all GNU extensions\n\ + -H, --hashsize=PRIME set symbol lookup hash table size [509]\n\ + -L, --nesting-limit=NUMBER change nesting limit, 0 for unlimited [%d]\n\ +"), nesting_limit); + puts (""); + fputs ("\ +Frozen state files:\n\ + -F, --freeze-state=FILE produce a frozen state on FILE at end\n\ + -R, --reload-state=FILE reload a frozen state from FILE at start\n\ +", stdout); + fputs ("\ +\n\ +Debugging:\n\ + -d, --debug[=FLAGS] set debug level (no FLAGS implies `aeq')\n\ + --debugfile[=FILE] redirect debug and trace output to FILE\n\ + (default stderr, discard if empty string)\n\ + -l, --arglength=NUM restrict macro tracing size\n\ + -t, --trace=NAME trace NAME when it is defined\n\ +", stdout); + fputs ("\ +\n\ +FLAGS is any of:\n\ + a show actual arguments\n\ + c show before collect, after collect and after call\n\ + e show expansion\n\ + f say current input file name\n\ + i show changes in input files\n\ + l say current input line number\n\ + p show results of path searches\n\ + q quote values as necessary, with a or e flag\n\ + t trace for all macro calls, not only traceon'ed\n\ + x add a unique macro call id, useful with c flag\n\ + V shorthand for all of the above flags\n\ +", stdout); + fputs ("\ +\n\ +If defined, the environment variable `M4PATH' is a colon-separated list\n\ +of directories included after any specified by `-I'.\n\ +", stdout); + fputs ("\ +\n\ +Exit status is 0 for success, 1 for failure, 63 for frozen file version\n\ +mismatch, or whatever value was passed to the m4exit macro.\n\ +", stdout); + emit_bug_reporting_address (); + } + exit (status); +} + +/*--------------------------------------. +| Decode options and launch execution. | +`--------------------------------------*/ + +/* For long options that have no equivalent short option, use a + non-character as a pseudo short option, starting with CHAR_MAX + 1. */ +enum +{ + DEBUGFILE_OPTION = CHAR_MAX + 1, /* no short opt */ + DIVERSIONS_OPTION, /* not quite -N, because of message */ + WARN_MACRO_SEQUENCE_OPTION, /* no short opt */ + + HELP_OPTION, /* no short opt */ + VERSION_OPTION /* no short opt */ +}; + +static const struct option long_options[] = +{ + {"arglength", required_argument, NULL, 'l'}, + {"debug", optional_argument, NULL, 'd'}, + {"define", required_argument, NULL, 'D'}, + {"error-output", required_argument, NULL, 'o'}, /* FIXME: deprecate in 2.0 */ + {"fatal-warnings", no_argument, NULL, 'E'}, + {"freeze-state", required_argument, NULL, 'F'}, + {"gnu", no_argument, NULL, 'g'}, + {"hashsize", required_argument, NULL, 'H'}, + {"include", required_argument, NULL, 'I'}, + {"interactive", no_argument, NULL, 'i'}, + {"nesting-limit", required_argument, NULL, 'L'}, + {"prefix-builtins", no_argument, NULL, 'P'}, + {"quiet", no_argument, NULL, 'Q'}, + {"reload-state", required_argument, NULL, 'R'}, + {"silent", no_argument, NULL, 'Q'}, + {"synclines", no_argument, NULL, 's'}, + {"trace", required_argument, NULL, 't'}, + {"traditional", no_argument, NULL, 'G'}, + {"undefine", required_argument, NULL, 'U'}, + {"word-regexp", required_argument, NULL, 'W'}, + + {"debugfile", optional_argument, NULL, DEBUGFILE_OPTION}, + {"diversions", required_argument, NULL, DIVERSIONS_OPTION}, + {"warn-macro-sequence", optional_argument, NULL, WARN_MACRO_SEQUENCE_OPTION}, + + {"help", no_argument, NULL, HELP_OPTION}, + {"version", no_argument, NULL, VERSION_OPTION}, + + { NULL, 0, NULL, 0 }, +}; + +/* Process a command line file NAME, and return true only if it was + stdin. */ +static void +process_file (const char *name) +{ + if (STREQ (name, "-")) + { + /* If stdin is a terminal, we want to allow 'm4 - file -' + to read input from stdin twice, like GNU cat. Besides, + there is no point closing stdin before wrapped text, to + minimize bugs in syscmd called from wrapped text. */ + push_file (stdin, "stdin", false); + } + else + { + char *full_name; + FILE *fp = m4_path_search (name, &full_name); + if (fp == NULL) + { + error (0, errno, _("cannot open `%s'"), name); + /* Set the status to EXIT_FAILURE, even though we + continue to process files after a missing file. */ + retcode = EXIT_FAILURE; + return; + } + push_file (fp, full_name, true); + free (full_name); + } + expand_input (); +} + +/* POSIX requires only -D, -U, and -s; and says that the first two + must be recognized when interspersed with file names. Traditional + behavior also handles -s between files. Starting OPTSTRING with + '-' forces getopt_long to hand back file names as arguments to opt + '\1', rather than reordering the command line. */ +#ifdef ENABLE_CHANGEWORD +#define OPTSTRING "-B:D:EF:GH:I:L:N:PQR:S:T:U:W:d::egil:o:st:" +#else +#define OPTSTRING "-B:D:EF:GH:I:L:N:PQR:S:T:U:d::egil:o:st:" +#endif + +int +main (int argc, char *const *argv) +{ + struct sigaction act; + macro_definition *head; /* head of deferred argument list */ + macro_definition *tail; + macro_definition *defn; + int optchar; /* option character */ + + macro_definition *defines; + bool interactive = false; + bool seen_file = false; + const char *debugfile = NULL; + const char *frozen_file_to_read = NULL; + const char *frozen_file_to_write = NULL; + const char *macro_sequence = ""; + + set_program_name (argv[0]); + retcode = EXIT_SUCCESS; + atexit (close_stdin); + + include_init (); + debug_init (); + + /* Stack overflow and program error handling. Ignore failure to + install a handler, since this is merely for improved output on + crash, and we should never crash ;). We install SIGBUS and + SIGSEGV handlers prior to using the c-stack module; depending on + the platform, c-stack will then override none, SIGSEGV, or both + handlers. */ + program_error_message + = xasprintf (_("internal error detected; please report this bug to <%s>"), + PACKAGE_BUGREPORT); + signal_message[SIGSEGV] = xstrdup (strsignal (SIGSEGV)); + signal_message[SIGABRT] = xstrdup (strsignal (SIGABRT)); + signal_message[SIGILL] = xstrdup (strsignal (SIGILL)); + signal_message[SIGFPE] = xstrdup (strsignal (SIGFPE)); + if (SIGBUS != SIGILL && SIGBUS != SIGSEGV) + signal_message[SIGBUS] = xstrdup (strsignal (SIGBUS)); + sigemptyset (&act.sa_mask); + /* One-shot - if we fault while handling a fault, we want to revert + to default signal behavior. */ + act.sa_flags = SA_NODEFER | SA_RESETHAND; + act.sa_handler = fault_handler; + sigaction (SIGSEGV, &act, NULL); + sigaction (SIGABRT, &act, NULL); + sigaction (SIGILL, &act, NULL); + sigaction (SIGFPE, &act, NULL); + sigaction (SIGBUS, &act, NULL); + if (c_stack_action (fault_handler) == 0) + nesting_limit = 0; + +#ifdef DEBUG_STKOVF + /* Make it easier to test our fault handlers. Exporting M4_CRASH=0 + attempts a SIGSEGV, exporting it as 1 attempts an assertion + failure with a fallback to abort. */ + { + char *crash = getenv ("M4_CRASH"); + if (crash) + { + if (!strtol (crash, NULL, 10)) + ++*(int *) 8; + assert (false); + abort (); + } + } +#endif /* DEBUG_STKOVF */ + + /* First, we decode the arguments, to size up tables and stuff. */ + head = tail = NULL; + + while ((optchar = getopt_long (argc, (char **) argv, OPTSTRING, + long_options, NULL)) != -1) + switch (optchar) + { + default: + usage (EXIT_FAILURE); + + case 'B': + case 'S': + case 'T': + /* Compatibility junk: options that other implementations + support, but which we ignore as no-ops and don't list in + --help. */ + error (0, 0, _("warning: `m4 -%c' may be removed in a future release"), + optchar); + break; + + case 'N': + case DIVERSIONS_OPTION: + /* -N became an obsolete no-op in 1.4.x. */ + error (0, 0, _("warning: `m4 %s' is deprecated"), + optchar == 'N' ? "-N" : "--diversions"); + break; + + case 'D': + case 'U': + case 's': + case 't': + case '\1': + case DEBUGFILE_OPTION: + /* Arguments that cannot be handled until later are accumulated. */ + + defn = (macro_definition *) xmalloc (sizeof (macro_definition)); + defn->code = optchar; + defn->arg = optarg; + defn->next = NULL; + + if (head == NULL) + head = defn; + else + tail->next = defn; + tail = defn; + + break; + + case 'E': + if (! fatal_warnings) + fatal_warnings = true; + else + warning_status = EXIT_FAILURE; + break; + + case 'F': + frozen_file_to_write = optarg; + break; + + case 'G': + no_gnu_extensions = 1; + break; + + case 'H': + hash_table_size = strtol (optarg, NULL, 10); + if (hash_table_size == 0) + hash_table_size = HASHMAX; + break; + + case 'I': + add_include_directory (optarg); + break; + + case 'L': + nesting_limit = strtol (optarg, NULL, 10); + break; + + case 'P': + prefix_all_builtins = 1; + break; + + case 'Q': + suppress_warnings = 1; + break; + + case 'R': + frozen_file_to_read = optarg; + break; + +#ifdef ENABLE_CHANGEWORD + case 'W': + user_word_regexp = optarg; + break; +#endif + + case 'd': + debug_level = debug_decode (optarg); + if (debug_level < 0) + { + error (0, 0, _("bad debug flags: `%s'"), optarg); + debug_level = 0; + } + break; + + case 'e': + error (0, 0, _("warning: `m4 -e' is deprecated, use `-i' instead")); + /* fall through */ + case 'i': + interactive = true; + break; + + case 'g': + no_gnu_extensions = 0; + break; + + case 'l': + max_debug_argument_length = strtol (optarg, NULL, 10); + if (max_debug_argument_length <= 0) + max_debug_argument_length = 0; + break; + + case 'o': + /* -o/--error-output are deprecated synonyms of --debugfile, + but don't issue a deprecation warning until autoconf 2.61 + or later is more widely established, as such a warning + would interfere with all earlier versions of autoconf. */ + /* Don't call debug_set_output here, as it has side effects. */ + debugfile = optarg; + break; + + case WARN_MACRO_SEQUENCE_OPTION: + /* Don't call set_macro_sequence here, as it can exit. + --warn-macro-sequence sets optarg to NULL (which uses the + default regexp); --warn-macro-sequence= sets optarg to "" + (which disables these warnings). */ + macro_sequence = optarg; + break; + + case VERSION_OPTION: + version_etc (stdout, PACKAGE, PACKAGE_NAME, VERSION, AUTHORS, NULL); + exit (EXIT_SUCCESS); + break; + + case HELP_OPTION: + usage (EXIT_SUCCESS); + break; + } + + defines = head; + + /* Do the basic initializations. */ + if (debugfile && !debug_set_output (debugfile)) + M4ERROR ((warning_status, errno, "cannot set debug file `%s'", debugfile)); + + input_init (); + output_init (); + symtab_init (); + set_macro_sequence (macro_sequence); + include_env_init (); + + if (frozen_file_to_read) + reload_frozen_state (frozen_file_to_read); + else + builtin_init (); + + /* Interactive mode means unbuffered output, and interrupts ignored. */ + + if (interactive) + { + signal (SIGINT, SIG_IGN); + setbuf (stdout, (char *) NULL); + } + + /* Handle deferred command line macro definitions. Must come after + initialization of the symbol table. */ + + while (defines != NULL) + { + macro_definition *next; + symbol *sym; + + switch (defines->code) + { + case 'D': + { + /* defines->arg is read-only, so we need a copy. */ + char *macro_name = xstrdup (defines->arg); + char *macro_value = strchr (macro_name, '='); + if (macro_value) + *macro_value++ = '\0'; + define_user_macro (macro_name, macro_value, SYMBOL_INSERT); + free (macro_name); + } + break; + + case 'U': + lookup_symbol (defines->arg, SYMBOL_DELETE); + break; + + case 't': + sym = lookup_symbol (defines->arg, SYMBOL_INSERT); + SYMBOL_TRACED (sym) = true; + break; + + case 's': + sync_output = 1; + break; + + case '\1': + seen_file = true; + process_file (defines->arg); + break; + + case DEBUGFILE_OPTION: + if (!debug_set_output (defines->arg)) + M4ERROR ((warning_status, errno, "cannot set debug file `%s'", + debugfile ? debugfile : _("stderr"))); + break; + + default: + M4ERROR ((0, 0, "INTERNAL ERROR: bad code in deferred arguments")); + abort (); + } + + next = defines->next; + free (defines); + defines = next; + } + + /* Handle remaining input files. Each file is pushed on the input, + and the input read. Wrapup text is handled separately later. */ + + if (optind == argc && !seen_file) + process_file ("-"); + else + for (; optind < argc; optind++) + process_file (argv[optind]); + + /* Now handle wrapup text. */ + + while (pop_wrapup ()) + expand_input (); + + /* Change debug stream back to stderr, to force flushing the debug + stream and detect any errors it might have encountered. The + three standard streams are closed by close_stdin. */ + debug_set_output (NULL); + + if (frozen_file_to_write) + produce_frozen_state (frozen_file_to_write); + else + { + make_diversion (0); + undivert_all (); + } + output_exit (); + free_macro_sequence (); + exit (retcode); +} diff --git a/src/m4.h b/src/m4.h new file mode 100644 index 0000000..3708a7c --- /dev/null +++ b/src/m4.h @@ -0,0 +1,487 @@ +/* GNU m4 -- A simple macro processor + + Copyright (C) 1989-1994, 2004-2011 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/>. +*/ + +/* We use <config.h> instead of "config.h" so that a compilation + using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h + (which it would do because it found this file in $srcdir). */ + +#include <config.h> + +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "binary-io.h" +#include "clean-temp.h" +#include "cloexec.h" +#include "close-stream.h" +#include "closein.h" +#include "dirname.h" +#include "error.h" +#include "exitfail.h" +#include "filenamecat.h" +#include "obstack.h" +#include "stdio--.h" +#include "stdlib--.h" +#include "unistd--.h" +#include "verror.h" +#include "xalloc.h" +#include "xprintf.h" +#include "xvasprintf.h" + +/* Canonicalize UNIX recognition macros. */ +#if defined unix || defined __unix || defined __unix__ \ + || defined _POSIX_VERSION || defined _POSIX2_VERSION \ + || defined __NetBSD__ || defined __OpenBSD__ \ + || defined __APPLE__ || defined __APPLE_CC__ +# define UNIX 1 +#endif + +/* Canonicalize Windows recognition macros. */ +#if (defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__ +# define W32_NATIVE 1 +#endif + +/* Canonicalize OS/2 recognition macro. */ +#ifdef __EMX__ +# define OS2 1 +# undef UNIX +#endif + +/* Used if any programmer error is detected (not possible, right?) */ +#define EXIT_INTERNAL_ERROR 2 + +/* Used for version mismatch, when -R detects a frozen file it can't parse. */ +#define EXIT_MISMATCH 63 + +/* No-op, for future gettext compatibility. */ +#define _(ARG) ARG + +/* Various declarations. */ + +struct string + { + char *string; /* characters of the string */ + size_t length; /* length of the string */ + }; +typedef struct string STRING; + +/* Memory allocation. */ +#define obstack_chunk_alloc xmalloc +#define obstack_chunk_free free + +/* Those must come first. */ +typedef struct token_data token_data; +typedef void builtin_func (struct obstack *, int, token_data **); + +/* Gnulib's stdbool doesn't work with bool bitfields. For nicer + debugging, use bool when we know it works, but use the more + portable unsigned int elsewhere. */ +#if __GNUC__ > 2 +typedef bool bool_bitfield; +#else +typedef unsigned int bool_bitfield; +#endif /* ! __GNUC__ */ + +/* Take advantage of GNU C compiler source level optimization hints, + using portable macros. */ +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 6) +# define M4_GNUC_ATTRIBUTE(args) __attribute__ (args) +#else +# define M4_GNUC_ATTRIBUTE(args) +#endif /* __GNUC__ */ + +#define M4_GNUC_UNUSED M4_GNUC_ATTRIBUTE ((__unused__)) +#define M4_GNUC_PRINTF(fmt, arg) \ + M4_GNUC_ATTRIBUTE ((__format__ (__printf__, fmt, arg))) +#define M4_GNUC_NORETURN M4_GNUC_ATTRIBUTE ((__noreturn__)) + +/* File: m4.c --- global definitions. */ + +/* Option flags. */ +extern int sync_output; /* -s */ +extern int debug_level; /* -d */ +extern size_t hash_table_size; /* -H */ +extern int no_gnu_extensions; /* -G */ +extern int prefix_all_builtins; /* -P */ +extern int max_debug_argument_length; /* -l */ +extern int suppress_warnings; /* -Q */ +extern int warning_status; /* -E */ +extern int nesting_limit; /* -L */ +#ifdef ENABLE_CHANGEWORD +extern const char *user_word_regexp; /* -W */ +#endif + +/* Error handling. */ +extern int retcode; +extern const char *program_name; + +void m4_error (int, int, const char *, ...) M4_GNUC_PRINTF(3, 4); +void m4_error_at_line (int, int, const char *, int, + const char *, ...) M4_GNUC_PRINTF(5, 6); + +#define M4ERROR(Arglist) (m4_error Arglist) +#define M4ERROR_AT_LINE(Arglist) (m4_error_at_line Arglist) + + +/* File: debug.c --- debugging and tracing function. */ + +extern FILE *debug; + +/* The value of debug_level is a bitmask of the following. */ + +/* a: show arglist in trace output */ +#define DEBUG_TRACE_ARGS 1 +/* e: show expansion in trace output */ +#define DEBUG_TRACE_EXPANSION 2 +/* q: quote args and expansion in trace output */ +#define DEBUG_TRACE_QUOTE 4 +/* t: trace all macros -- overrides trace{on,off} */ +#define DEBUG_TRACE_ALL 8 +/* l: add line numbers to trace output */ +#define DEBUG_TRACE_LINE 16 +/* f: add file name to trace output */ +#define DEBUG_TRACE_FILE 32 +/* p: trace path search of include files */ +#define DEBUG_TRACE_PATH 64 +/* c: show macro call before args collection */ +#define DEBUG_TRACE_CALL 128 +/* i: trace changes of input files */ +#define DEBUG_TRACE_INPUT 256 +/* x: add call id to trace output */ +#define DEBUG_TRACE_CALLID 512 + +/* V: very verbose -- print everything */ +#define DEBUG_TRACE_VERBOSE 1023 +/* default flags -- equiv: aeq */ +#define DEBUG_TRACE_DEFAULT 7 + +#define DEBUG_PRINT1(Fmt, Arg1) \ + do \ + { \ + if (debug != NULL) \ + xfprintf (debug, Fmt, Arg1); \ + } \ + while (0) + +#define DEBUG_PRINT3(Fmt, Arg1, Arg2, Arg3) \ + do \ + { \ + if (debug != NULL) \ + xfprintf (debug, Fmt, Arg1, Arg2, Arg3); \ + } \ + while (0) + +#define DEBUG_MESSAGE(Fmt) \ + do \ + { \ + if (debug != NULL) \ + { \ + debug_message_prefix (); \ + xfprintf (debug, Fmt); \ + putc ('\n', debug); \ + } \ + } \ + while (0) + +#define DEBUG_MESSAGE1(Fmt, Arg1) \ + do \ + { \ + if (debug != NULL) \ + { \ + debug_message_prefix (); \ + xfprintf (debug, Fmt, Arg1); \ + putc ('\n', debug); \ + } \ + } \ + while (0) + +#define DEBUG_MESSAGE2(Fmt, Arg1, Arg2) \ + do \ + { \ + if (debug != NULL) \ + { \ + debug_message_prefix (); \ + xfprintf (debug, Fmt, Arg1, Arg2); \ + putc ('\n', debug); \ + } \ + } \ + while (0) + +void debug_init (void); +int debug_decode (const char *); +void debug_flush_files (void); +bool debug_set_output (const char *); +void debug_message_prefix (void); + +void trace_prepre (const char *, int); +void trace_pre (const char *, int, int, token_data **); +void trace_post (const char *, int, int, const char *); + +/* File: input.c --- lexical definitions. */ + +/* Various different token types. */ +enum token_type +{ + TOKEN_EOF, /* end of file */ + TOKEN_STRING, /* a quoted string or comment */ + TOKEN_WORD, /* an identifier */ + TOKEN_OPEN, /* ( */ + TOKEN_COMMA, /* , */ + TOKEN_CLOSE, /* ) */ + TOKEN_SIMPLE, /* any other single character */ + TOKEN_MACDEF /* a macro's definition (see "defn") */ +}; + +/* The data for a token, a macro argument, and a macro definition. */ +enum token_data_type +{ + TOKEN_VOID, + TOKEN_TEXT, + TOKEN_FUNC +}; + +struct token_data +{ + enum token_data_type type; + union + { + struct + { + char *text; +#ifdef ENABLE_CHANGEWORD + char *original_text; +#endif + } + u_t; + builtin_func *func; + } + u; +}; + +#define TOKEN_DATA_TYPE(Td) ((Td)->type) +#define TOKEN_DATA_TEXT(Td) ((Td)->u.u_t.text) +#ifdef ENABLE_CHANGEWORD +# define TOKEN_DATA_ORIG_TEXT(Td) ((Td)->u.u_t.original_text) +#endif +#define TOKEN_DATA_FUNC(Td) ((Td)->u.func) + +typedef enum token_type token_type; +typedef enum token_data_type token_data_type; + +void input_init (void); +token_type peek_token (void); +token_type next_token (token_data *, int *); +void skip_line (void); + +/* push back input */ +void push_file (FILE *, const char *, bool); +void push_macro (builtin_func *); +struct obstack *push_string_init (void); +const char *push_string_finish (void); +void push_wrapup (const char *); +bool pop_wrapup (void); + +/* current input file, and line */ +extern const char *current_file; +extern int current_line; + +/* left and right quote, begin and end comment */ +extern STRING bcomm, ecomm; +extern STRING lquote, rquote; + +#define DEF_LQUOTE "`" +#define DEF_RQUOTE "\'" +#define DEF_BCOMM "#" +#define DEF_ECOMM "\n" + +void set_quotes (const char *, const char *); +void set_comment (const char *, const char *); +#ifdef ENABLE_CHANGEWORD +void set_word_regexp (const char *); +#endif + +/* File: output.c --- output functions. */ +extern int current_diversion; +extern int output_current_line; + +void output_init (void); +void output_exit (void); +void output_text (const char *, int); +void shipout_text (struct obstack *, const char *, int, int); +void make_diversion (int); +void insert_diversion (int); +void insert_file (FILE *); +void freeze_diversions (FILE *); + +/* File symtab.c --- symbol table definitions. */ + +/* Operation modes for lookup_symbol (). */ +enum symbol_lookup +{ + SYMBOL_LOOKUP, + SYMBOL_INSERT, + SYMBOL_DELETE, + SYMBOL_PUSHDEF, + SYMBOL_POPDEF +}; + +/* Symbol table entry. */ +struct symbol +{ + struct symbol *next; + bool_bitfield traced : 1; + bool_bitfield shadowed : 1; + bool_bitfield macro_args : 1; + bool_bitfield blind_no_args : 1; + bool_bitfield deleted : 1; + int pending_expansions; + + char *name; + token_data data; +}; + +#define SYMBOL_NEXT(S) ((S)->next) +#define SYMBOL_TRACED(S) ((S)->traced) +#define SYMBOL_SHADOWED(S) ((S)->shadowed) +#define SYMBOL_MACRO_ARGS(S) ((S)->macro_args) +#define SYMBOL_BLIND_NO_ARGS(S) ((S)->blind_no_args) +#define SYMBOL_DELETED(S) ((S)->deleted) +#define SYMBOL_PENDING_EXPANSIONS(S) ((S)->pending_expansions) +#define SYMBOL_NAME(S) ((S)->name) +#define SYMBOL_TYPE(S) (TOKEN_DATA_TYPE (&(S)->data)) +#define SYMBOL_TEXT(S) (TOKEN_DATA_TEXT (&(S)->data)) +#define SYMBOL_FUNC(S) (TOKEN_DATA_FUNC (&(S)->data)) + +typedef enum symbol_lookup symbol_lookup; +typedef struct symbol symbol; +typedef void hack_symbol (symbol *, void *); + +#define HASHMAX 509 /* default, overridden by -Hsize */ + +extern symbol **symtab; + +void free_symbol (symbol *sym); +void symtab_init (void); +symbol *lookup_symbol (const char *, symbol_lookup); +void hack_all_symbols (hack_symbol *, void *); + +/* File: macro.c --- macro expansion. */ + +void expand_input (void); +void call_macro (symbol *, int, token_data **, struct obstack *); + +/* File: builtin.c --- builtins. */ + +struct builtin +{ + const char *name; + bool_bitfield gnu_extension : 1; + bool_bitfield groks_macro_args : 1; + bool_bitfield blind_if_no_args : 1; + builtin_func *func; +}; + +struct predefined +{ + const char *unix_name; + const char *gnu_name; + const char *func; +}; + +typedef struct builtin builtin; +typedef struct predefined predefined; +struct re_pattern_buffer; +struct re_registers; + +/* The default sequence detects multi-digit parameters (obsolete after + 1.4.x), and any use of extended arguments with the default ${} + syntax (new in 2.0). */ +#define DEFAULT_MACRO_SEQUENCE "\\$\\({[^}]*}\\|[0-9][0-9]+\\)" + +void builtin_init (void); +void define_builtin (const char *, const builtin *, symbol_lookup); +void set_macro_sequence (const char *); +void free_macro_sequence (void); +void define_user_macro (const char *, const char *, symbol_lookup); +void undivert_all (void); +void expand_user_macro (struct obstack *, symbol *, int, token_data **); +void m4_placeholder (struct obstack *, int, token_data **); +void init_pattern_buffer (struct re_pattern_buffer *, struct re_registers *); +const char *ntoa (int32_t, int); + +const builtin *find_builtin_by_addr (builtin_func *); +const builtin *find_builtin_by_name (const char *); + +/* File: path.c --- path search for include files. */ + +void include_init (void); +void include_env_init (void); +void add_include_directory (const char *); +FILE *m4_path_search (const char *, char **); + +/* File: eval.c --- expression evaluation. */ + +bool evaluate (const char *, int32_t *); + +/* File: format.c --- printf like formatting. */ + +void expand_format (struct obstack *, int, token_data **); + +/* File: freeze.c --- frozen state files. */ + +void produce_frozen_state (const char *); +void reload_frozen_state (const char *); + +/* Debugging the memory allocator. */ + +#ifdef WITH_DMALLOC +# define DMALLOC_FUNC_CHECK +# include <dmalloc.h> +#endif + +/* Other debug stuff. */ + +#ifdef DEBUG +# define DEBUG_INCL 1 +# define DEBUG_INPUT 1 +# define DEBUG_MACRO 1 +# define DEBUG_OUTPUT 1 +# define DEBUG_STKOVF 1 +# define DEBUG_SYM 1 +#endif + +/* Convert a possibly-signed character to an unsigned character. This is + a bit safer than casting to unsigned char, since it catches some type + errors that the cast doesn't. */ +#if HAVE_INLINE +static inline unsigned char to_uchar (char ch) { return ch; } +#else +# define to_uchar(C) ((unsigned char) (C)) +#endif + +/* Avoid negative logic when comparing two strings. */ +#define STREQ(a, b) (strcmp (a, b) == 0) diff --git a/src/macro.c b/src/macro.c new file mode 100644 index 0000000..e2e2499 --- /dev/null +++ b/src/macro.c @@ -0,0 +1,391 @@ +/* GNU m4 -- A simple macro processor + + Copyright (C) 1989-1994, 2006-2007, 2009-2011 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/>. +*/ + +/* This file contains the functions, that performs the basic argument + parsing and macro expansion. */ + +#include "m4.h" + +static void expand_macro (symbol *); +static void expand_token (struct obstack *, token_type, token_data *, int); + +/* Current recursion level in expand_macro (). */ +int expansion_level = 0; + +/* The number of the current call of expand_macro (). */ +static int macro_call_id = 0; + +/* The shared stack of collected arguments for macro calls; as each + argument is collected, it is finished and its location stored in + argv_stack. Normally, this stack can be used simultaneously by + multiple macro calls; the exception is when an outer macro has + generated some text, then calls a nested macro, in which case the + nested macro must use a local stack to leave the unfinished text + alone. Too bad obstack.h does not provide an easy way to reopen a + finished object for further growth, but in practice this does not + hurt us too much. */ +static struct obstack argc_stack; + +/* The shared stack of pointers to collected arguments for macro + calls. This object is never finished; we exploit the fact that + obstack_blank is documented to take a negative size to reduce the + size again. */ +static struct obstack argv_stack; + +/*----------------------------------------------------------------------. +| This function read all input, and expands each token, one at a time. | +`----------------------------------------------------------------------*/ + +void +expand_input (void) +{ + token_type t; + token_data td; + int line; + + obstack_init (&argc_stack); + obstack_init (&argv_stack); + + while ((t = next_token (&td, &line)) != TOKEN_EOF) + expand_token ((struct obstack *) NULL, t, &td, line); + + obstack_free (&argc_stack, NULL); + obstack_free (&argv_stack, NULL); +} + + +/*----------------------------------------------------------------. +| Expand one token, according to its type. Potential macro names | +| (TOKEN_WORD) are looked up in the symbol table, to see if they | +| have a macro definition. If they have, they are expanded as | +| macros, otherwise the text is just copied to the output. | +`----------------------------------------------------------------*/ + +static void +expand_token (struct obstack *obs, token_type t, token_data *td, int line) +{ + symbol *sym; + + switch (t) + { /* TOKSW */ + case TOKEN_EOF: + case TOKEN_MACDEF: + break; + + case TOKEN_OPEN: + case TOKEN_COMMA: + case TOKEN_CLOSE: + case TOKEN_SIMPLE: + case TOKEN_STRING: + shipout_text (obs, TOKEN_DATA_TEXT (td), strlen (TOKEN_DATA_TEXT (td)), + line); + break; + + case TOKEN_WORD: + sym = lookup_symbol (TOKEN_DATA_TEXT (td), SYMBOL_LOOKUP); + if (sym == NULL || SYMBOL_TYPE (sym) == TOKEN_VOID + || (SYMBOL_TYPE (sym) == TOKEN_FUNC + && SYMBOL_BLIND_NO_ARGS (sym) + && peek_token () != TOKEN_OPEN)) + { +#ifdef ENABLE_CHANGEWORD + shipout_text (obs, TOKEN_DATA_ORIG_TEXT (td), + strlen (TOKEN_DATA_ORIG_TEXT (td)), line); +#else + shipout_text (obs, TOKEN_DATA_TEXT (td), + strlen (TOKEN_DATA_TEXT (td)), line); +#endif + } + else + expand_macro (sym); + break; + + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: bad token type in expand_token ()")); + abort (); + } +} + + +/*-------------------------------------------------------------------. +| This function parses one argument to a macro call. It expects the | +| first left parenthesis, or the separating comma, to have been read | +| by the caller. It skips leading whitespace, and reads and expands | +| tokens, until it finds a comma or an right parenthesis at the same | +| level of parentheses. It returns a flag indicating whether the | +| argument read is the last for the active macro call. The argument | +| is built on the obstack OBS, indirectly through expand_token (). | +`-------------------------------------------------------------------*/ + +static bool +expand_argument (struct obstack *obs, token_data *argp) +{ + token_type t; + token_data td; + char *text; + int paren_level; + const char *file = current_file; + int line = current_line; + + TOKEN_DATA_TYPE (argp) = TOKEN_VOID; + + /* Skip leading white space. */ + do + { + t = next_token (&td, NULL); + } + while (t == TOKEN_SIMPLE && isspace (to_uchar (*TOKEN_DATA_TEXT (&td)))); + + paren_level = 0; + + while (1) + { + + switch (t) + { /* TOKSW */ + case TOKEN_COMMA: + case TOKEN_CLOSE: + if (paren_level == 0) + { + /* The argument MUST be finished, whether we want it or not. */ + obstack_1grow (obs, '\0'); + text = (char *) obstack_finish (obs); + + if (TOKEN_DATA_TYPE (argp) == TOKEN_VOID) + { + TOKEN_DATA_TYPE (argp) = TOKEN_TEXT; + TOKEN_DATA_TEXT (argp) = text; + } + return t == TOKEN_COMMA; + } + /* fallthru */ + case TOKEN_OPEN: + case TOKEN_SIMPLE: + text = TOKEN_DATA_TEXT (&td); + + if (*text == '(') + paren_level++; + else if (*text == ')') + paren_level--; + expand_token (obs, t, &td, line); + break; + + case TOKEN_EOF: + /* current_file changed to "" if we see TOKEN_EOF, use the + previous value we stored earlier. */ + M4ERROR_AT_LINE ((EXIT_FAILURE, 0, file, line, + "ERROR: end of file in argument list")); + break; + + case TOKEN_WORD: + case TOKEN_STRING: + expand_token (obs, t, &td, line); + break; + + case TOKEN_MACDEF: + if (obstack_object_size (obs) == 0) + { + TOKEN_DATA_TYPE (argp) = TOKEN_FUNC; + TOKEN_DATA_FUNC (argp) = TOKEN_DATA_FUNC (&td); + } + break; + + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: bad token type in expand_argument ()")); + abort (); + } + + t = next_token (&td, NULL); + } +} + +/*-------------------------------------------------------------. +| Collect all the arguments to a call of the macro SYM. The | +| arguments are stored on the obstack ARGUMENTS and a table of | +| pointers to the arguments on the obstack ARGPTR. | +`-------------------------------------------------------------*/ + +static void +collect_arguments (symbol *sym, struct obstack *argptr, + struct obstack *arguments) +{ + token_data td; + token_data *tdp; + bool more_args; + bool groks_macro_args = SYMBOL_MACRO_ARGS (sym); + + TOKEN_DATA_TYPE (&td) = TOKEN_TEXT; + TOKEN_DATA_TEXT (&td) = SYMBOL_NAME (sym); + tdp = (token_data *) obstack_copy (arguments, &td, sizeof td); + obstack_ptr_grow (argptr, tdp); + + if (peek_token () == TOKEN_OPEN) + { + next_token (&td, NULL); /* gobble parenthesis */ + do + { + more_args = expand_argument (arguments, &td); + + if (!groks_macro_args && TOKEN_DATA_TYPE (&td) == TOKEN_FUNC) + { + TOKEN_DATA_TYPE (&td) = TOKEN_TEXT; + TOKEN_DATA_TEXT (&td) = (char *) ""; + } + tdp = (token_data *) obstack_copy (arguments, &td, sizeof td); + obstack_ptr_grow (argptr, tdp); + } + while (more_args); + } +} + + +/*-------------------------------------------------------------------. +| The actual call of a macro is handled by call_macro (). | +| call_macro () is passed a symbol SYM, whose type is used to call | +| either a builtin function, or the user macro expansion function | +| expand_user_macro () (lives in builtin.c). There are ARGC | +| arguments to the call, stored in the ARGV table. The expansion is | +| left on the obstack EXPANSION. Macro tracing is also handled | +| here. | +`-------------------------------------------------------------------*/ + +void +call_macro (symbol *sym, int argc, token_data **argv, + struct obstack *expansion) +{ + switch (SYMBOL_TYPE (sym)) + { + case TOKEN_FUNC: + (*SYMBOL_FUNC (sym)) (expansion, argc, argv); + break; + + case TOKEN_TEXT: + expand_user_macro (expansion, sym, argc, argv); + break; + + case TOKEN_VOID: + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: bad symbol type in call_macro ()")); + abort (); + } +} + +/*-------------------------------------------------------------------. +| The macro expansion is handled by expand_macro (). It parses the | +| arguments, using collect_arguments (), and builds a table of | +| pointers to the arguments. The arguments themselves are stored on | +| a local obstack. Expand_macro () uses call_macro () to do the | +| call of the macro. | +| | +| Expand_macro () is potentially recursive, since it calls | +| expand_argument (), which might call expand_token (), which might | +| call expand_macro (). | +`-------------------------------------------------------------------*/ + +static void +expand_macro (symbol *sym) +{ + struct obstack arguments; /* Alternate obstack if argc_stack is busy. */ + unsigned argv_base; /* Size of argv_stack on entry. */ + bool use_argc_stack = true; /* Whether argc_stack is safe. */ + token_data **argv; + int argc; + struct obstack *expansion; + const char *expanded; + bool traced; + int my_call_id; + + /* Report errors at the location where the open parenthesis (if any) + was found, but after expansion, restore global state back to the + location of the close parenthesis. This is safe since we + guarantee that macro expansion does not alter the state of + current_file/current_line (dnl, include, and sinclude are special + cased in the input engine to ensure this fact). */ + const char *loc_open_file = current_file; + int loc_open_line = current_line; + const char *loc_close_file; + int loc_close_line; + + SYMBOL_PENDING_EXPANSIONS (sym)++; + expansion_level++; + if (nesting_limit > 0 && expansion_level > nesting_limit) + M4ERROR ((EXIT_FAILURE, 0, + "recursion limit of %d exceeded, use -L<N> to change it", + nesting_limit)); + + macro_call_id++; + my_call_id = macro_call_id; + + traced = (debug_level & DEBUG_TRACE_ALL) || SYMBOL_TRACED (sym); + + argv_base = obstack_object_size (&argv_stack); + if (obstack_object_size (&argc_stack) > 0) + { + /* We cannot use argc_stack if this is a nested invocation, and an + outer invocation has an unfinished argument being + collected. */ + obstack_init (&arguments); + use_argc_stack = false; + } + + if (traced && (debug_level & DEBUG_TRACE_CALL)) + trace_prepre (SYMBOL_NAME (sym), my_call_id); + + collect_arguments (sym, &argv_stack, + use_argc_stack ? &argc_stack : &arguments); + + argc = ((obstack_object_size (&argv_stack) - argv_base) + / sizeof (token_data *)); + argv = (token_data **) ((char *) obstack_base (&argv_stack) + argv_base); + + loc_close_file = current_file; + loc_close_line = current_line; + current_file = loc_open_file; + current_line = loc_open_line; + + if (traced) + trace_pre (SYMBOL_NAME (sym), my_call_id, argc, argv); + + expansion = push_string_init (); + call_macro (sym, argc, argv, expansion); + expanded = push_string_finish (); + + if (traced) + trace_post (SYMBOL_NAME (sym), my_call_id, argc, expanded); + + current_file = loc_close_file; + current_line = loc_close_line; + + --expansion_level; + --SYMBOL_PENDING_EXPANSIONS (sym); + + if (SYMBOL_DELETED (sym)) + free_symbol (sym); + + if (use_argc_stack) + obstack_free (&argc_stack, argv[0]); + else + obstack_free (&arguments, NULL); + obstack_blank (&argv_stack, -argc * sizeof (token_data *)); +} diff --git a/src/output.c b/src/output.c new file mode 100644 index 0000000..91ff9f0 --- /dev/null +++ b/src/output.c @@ -0,0 +1,1017 @@ +/* GNU m4 -- A simple macro processor + + Copyright (C) 1989-1994, 2004-2011 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 "m4.h" + +#include <limits.h> +#include <sys/stat.h> + +#include "gl_avltree_oset.h" +#include "gl_xoset.h" + +/* Size of initial in-memory buffer size for diversions. Small diversions + would usually fit in. */ +#define INITIAL_BUFFER_SIZE 512 + +/* Maximum value for the total of all in-memory buffer sizes for + diversions. */ +#define MAXIMUM_TOTAL_SIZE (512 * 1024) + +/* Size of buffer size to use while copying files. */ +#define COPY_BUFFER_SIZE (32 * 512) + +/* Output functions. Most of the complexity is for handling cpp like + sync lines. + + This code is fairly entangled with the code in input.c, and maybe it + belongs there? */ + +typedef struct temp_dir m4_temp_dir; + +/* When part of diversion_table, each struct m4_diversion either + represents an open file (zero size, non-NULL u.file), an in-memory + buffer (non-zero size, non-NULL u.buffer), or an unused placeholder + diversion (zero size, u is NULL, non-zero used indicates that a + file has been created). When not part of diversion_table, u.next + is a pointer to the free_list chain. */ + +typedef struct m4_diversion m4_diversion; + +struct m4_diversion + { + union + { + FILE *file; /* Diversion file on disk. */ + char *buffer; /* Malloc'd diversion buffer. */ + m4_diversion *next; /* Free-list pointer */ + } u; + int divnum; /* Which diversion this represents. */ + int size; /* Usable size before reallocation. */ + int used; /* Used buffer length, or tmp file exists. */ + }; + +/* Table of diversions 1 through INT_MAX. */ +static gl_oset_t diversion_table; + +/* Diversion 0 (not part of diversion_table). */ +static m4_diversion div0; + +/* Linked list of reclaimed diversion storage. */ +static m4_diversion *free_list; + +/* Obstack from which diversion storage is allocated. */ +static struct obstack diversion_storage; + +/* Total size of all in-memory buffer sizes. */ +static int total_buffer_size; + +/* The number of the currently active diversion. This variable is + maintained for the `divnum' builtin function. */ +int current_diversion; + +/* Current output diversion, NULL if output is being currently + discarded. output_diversion->u is guaranteed non-NULL except when + the diversion has never been used; use size to determine if it is a + malloc'd buffer or a FILE. output_diversion->used is 0 if u.file + is stdout, and non-zero if this is a malloc'd buffer or a temporary + diversion file. */ +static m4_diversion *output_diversion; + +/* Cache of output_diversion->u.file, only valid when + output_diversion->size is 0. */ +static FILE *output_file; + +/* Cache of output_diversion->u.buffer + output_diversion->used, only + valid when output_diversion->size is non-zero. */ +static char *output_cursor; + +/* Cache of output_diversion->size - output_diversion->used, only + valid when output_diversion->size is non-zero. */ +static int output_unused; + +/* Number of input line we are generating output for. */ +int output_current_line; + +/* Temporary directory holding all spilled diversion files. */ +static m4_temp_dir *output_temp_dir; + +/* Cache of most recently used spilled diversion files. */ +static FILE *tmp_file1; +static FILE *tmp_file2; + +/* Diversions that own tmp_file, or 0. */ +static int tmp_file1_owner; +static int tmp_file2_owner; + +/* True if tmp_file2 is more recently used. */ +static bool tmp_file2_recent; + + +/* Internal routines. */ + +/* Callback for comparing list elements ELT1 and ELT2 for order in + diversion_table. */ +static int +cmp_diversion_CB (const void *elt1, const void *elt2) +{ + const m4_diversion *d1 = (const m4_diversion *) elt1; + const m4_diversion *d2 = (const m4_diversion *) elt2; + /* No need to worry about overflow, since we don't create diversions + with negative divnum. */ + return d1->divnum - d2->divnum; +} + +/* Callback for comparing list element ELT against THRESHOLD. */ +static bool +threshold_diversion_CB (const void *elt, const void *threshold) +{ + const m4_diversion *diversion = (const m4_diversion *) elt; + /* No need to worry about overflow, since we don't create diversions + with negative divnum. */ + return diversion->divnum >= *(const int *) threshold; +} + +/* Clean up any temporary directory. Designed for use as an atexit + handler, where it is not safe to call exit() recursively; so this + calls _exit if a problem is encountered. */ +static void +cleanup_tmpfile (void) +{ + /* Close any open diversions. */ + bool fail = false; + + if (diversion_table) + { + const void *elt; + gl_oset_iterator_t iter = gl_oset_iterator (diversion_table); + while (gl_oset_iterator_next (&iter, &elt)) + { + m4_diversion *diversion = (m4_diversion *) elt; + if (!diversion->size && diversion->u.file + && close_stream_temp (diversion->u.file) != 0) + { + M4ERROR ((0, errno, + "cannot clean temporary file for diversion")); + fail = true; + } + } + gl_oset_iterator_free (&iter); + } + + /* Clean up the temporary directory. */ + if (cleanup_temp_dir (output_temp_dir) != 0) + fail = true; + if (fail) + _exit (exit_failure); +} + +/* Convert DIVNUM into a temporary file name for use in m4_tmp*. */ +static const char * +m4_tmpname (int divnum) +{ + static char *buffer; + static char *tail; + if (buffer == NULL) + { + tail = xasprintf ("%s/m4-%d", output_temp_dir->dir_name, INT_MAX); + buffer = (char *) obstack_copy0 (&diversion_storage, tail, + strlen (tail)); + free (tail); + tail = strrchr (buffer, '-') + 1; + } + assert (0 < divnum); + sprintf (tail, "%d", divnum); + return buffer; +} + +/* Create a temporary file for diversion DIVNUM open for reading and + writing in a secure temp directory. The file will be automatically + closed and deleted on a fatal signal. The file can be closed and + reopened with m4_tmpclose and m4_tmpopen, or moved with + m4_tmprename; when finally done with the file, close it with + m4_tmpremove. Exits on failure, so the return value is always an + open file. */ +static FILE * +m4_tmpfile (int divnum) +{ + const char *name; + FILE *file; + + if (output_temp_dir == NULL) + { + output_temp_dir = create_temp_dir ("m4-", NULL, true); + if (output_temp_dir == NULL) + M4ERROR ((EXIT_FAILURE, errno, + "cannot create temporary file for diversion")); + atexit (cleanup_tmpfile); + } + name = m4_tmpname (divnum); + register_temp_file (output_temp_dir, name); + file = fopen_temp (name, O_BINARY ? "wb+" : "w+"); + if (file == NULL) + { + unregister_temp_file (output_temp_dir, name); + M4ERROR ((EXIT_FAILURE, errno, + "cannot create temporary file for diversion")); + } + else if (set_cloexec_flag (fileno (file), true) != 0) + M4ERROR ((warning_status, errno, + "Warning: cannot protect diversion across forks")); + return file; +} + +/* Reopen a temporary file for diversion DIVNUM for reading and + writing in a secure temp directory. If REREAD, the file is + positioned at offset 0, otherwise the file is positioned at the + end. Exits on failure, so the return value is always an open + file. */ +static FILE * +m4_tmpopen (int divnum, bool reread) +{ + const char *name; + FILE *file; + + if (tmp_file1_owner == divnum) + { + if (reread && fseeko (tmp_file1, 0, SEEK_SET) != 0) + m4_error (EXIT_FAILURE, errno, + _("cannot seek within diversion")); + tmp_file2_recent = false; + return tmp_file1; + } + else if (tmp_file2_owner == divnum) + { + if (reread && fseeko (tmp_file2, 0, SEEK_SET) != 0) + m4_error (EXIT_FAILURE, errno, + _("cannot seek within diversion")); + tmp_file2_recent = true; + return tmp_file2; + } + name = m4_tmpname (divnum); + /* We need update mode, to avoid truncation. */ + file = fopen_temp (name, O_BINARY ? "rb+" : "r+"); + if (file == NULL) + M4ERROR ((EXIT_FAILURE, errno, + "cannot create temporary file for diversion")); + else if (set_cloexec_flag (fileno (file), true) != 0) + m4_error (0, errno, _("cannot protect diversion across forks")); + /* Update mode starts at the beginning of the stream, but sometimes + we want the end. */ + else if (!reread && fseeko (file, 0, SEEK_END) != 0) + m4_error (EXIT_FAILURE, errno, + _("cannot seek within diversion")); + return file; +} + +/* Close, but don't delete, a temporary FILE for diversion DIVNUM. To + reduce the I/O overhead of repeatedly opening and closing the same + file, this implementation caches the most recent spilled diversion. + On the other hand, keeping every spilled diversion open would run + into EMFILE limits. */ +static int +m4_tmpclose (FILE *file, int divnum) +{ + int result = 0; + if (divnum != tmp_file1_owner && divnum != tmp_file2_owner) + { + if (tmp_file2_recent) + { + if (tmp_file1_owner) + result = close_stream_temp (tmp_file1); + tmp_file1 = file; + tmp_file1_owner = divnum; + } + else + { + if (tmp_file2_owner) + result = close_stream_temp (tmp_file2); + tmp_file2 = file; + tmp_file2_owner = divnum; + } + } + return result; +} + +/* Delete a closed temporary FILE for diversion DIVNUM. */ +static int +m4_tmpremove (int divnum) +{ + if (divnum == tmp_file1_owner) + { + int result = close_stream_temp (tmp_file1); + if (result) + return result; + tmp_file1_owner = 0; + } + else if (divnum == tmp_file2_owner) + { + int result = close_stream_temp (tmp_file2); + if (result) + return result; + tmp_file2_owner = 0; + } + return cleanup_temp_file (output_temp_dir, m4_tmpname (divnum)); +} + +/* Transfer the temporary file for diversion OLDNUM to the previously + unused diversion NEWNUM. Return an open stream visiting the new + temporary file, positioned at the end, or exit on failure. */ +static FILE* +m4_tmprename (int oldnum, int newnum) +{ + /* m4_tmpname reuses its return buffer. */ + char *oldname = xstrdup (m4_tmpname (oldnum)); + const char *newname = m4_tmpname (newnum); + register_temp_file (output_temp_dir, newname); + if (oldnum == tmp_file1_owner) + { + /* Be careful of mingw, which can't rename an open file. */ + if (RENAME_OPEN_FILE_WORKS) + tmp_file1_owner = newnum; + else + { + if (close_stream_temp (tmp_file1)) + m4_error (EXIT_FAILURE, errno, + _("cannot close temporary file for diversion")); + tmp_file1_owner = 0; + } + } + else if (oldnum == tmp_file2_owner) + { + /* Be careful of mingw, which can't rename an open file. */ + if (RENAME_OPEN_FILE_WORKS) + tmp_file2_owner = newnum; + else + { + if (close_stream_temp (tmp_file2)) + m4_error (EXIT_FAILURE, errno, + _("cannot close temporary file for diversion")); + tmp_file2_owner = 0; + } + } + /* Either it is safe to rename an open file, or no one should have + oldname open at this point. */ + if (rename (oldname, newname)) + m4_error (EXIT_FAILURE, errno, + _("cannot create temporary file for diversion")); + unregister_temp_file (output_temp_dir, oldname); + free (oldname); + return m4_tmpopen (newnum, false); +} + + +/*------------------------. +| Output initialization. | +`------------------------*/ + +void +output_init (void) +{ + diversion_table = gl_oset_create_empty (GL_AVLTREE_OSET, cmp_diversion_CB, + NULL); + div0.u.file = stdout; + output_diversion = &div0; + output_file = stdout; + obstack_init (&diversion_storage); +} + +void +output_exit (void) +{ + /* Order is important, since we may have registered cleanup_tmpfile + as an atexit handler, and it must not traverse stale memory. */ + gl_oset_t table = diversion_table; + if (tmp_file1_owner) + m4_tmpremove (tmp_file1_owner); + if (tmp_file2_owner) + m4_tmpremove (tmp_file2_owner); + diversion_table = NULL; + gl_oset_free (table); + obstack_free (&diversion_storage, NULL); +} + +/*----------------------------------------------------------------. +| Reorganize in-memory diversion buffers so the current diversion | +| can accomodate LENGTH more characters without further | +| reorganization. The current diversion buffer is made bigger if | +| possible. But to make room for a bigger buffer, one of the | +| in-memory diversion buffers might have to be flushed to a newly | +| created temporary file. This flushed buffer might well be the | +| current one. | +`----------------------------------------------------------------*/ + +static void +make_room_for (int length) +{ + int wanted_size; + m4_diversion *selected_diversion = NULL; + + /* Compute needed size for in-memory buffer. Diversions in-memory + buffers start at 0 bytes, then 512, then keep doubling until it is + decided to flush them to disk. */ + + output_diversion->used = output_diversion->size - output_unused; + + for (wanted_size = output_diversion->size; + wanted_size < output_diversion->used + length; + wanted_size = wanted_size == 0 ? INITIAL_BUFFER_SIZE : wanted_size * 2) + ; + + /* Check if we are exceeding the maximum amount of buffer memory. */ + + if (total_buffer_size - output_diversion->size + wanted_size + > MAXIMUM_TOTAL_SIZE) + { + int selected_used; + char *selected_buffer; + m4_diversion *diversion; + int count; + gl_oset_iterator_t iter; + const void *elt; + + /* Find out the buffer having most data, in view of flushing it to + disk. Fake the current buffer as having already received the + projected data, while making the selection. So, if it is + selected indeed, we will flush it smaller, before it grows. */ + + selected_diversion = output_diversion; + selected_used = output_diversion->used + length; + + iter = gl_oset_iterator (diversion_table); + while (gl_oset_iterator_next (&iter, &elt)) + { + diversion = (m4_diversion *) elt; + if (diversion->used > selected_used) + { + selected_diversion = diversion; + selected_used = diversion->used; + } + } + gl_oset_iterator_free (&iter); + + /* Create a temporary file, write the in-memory buffer of the + diversion to this file, then release the buffer. Zero the + diversion before doing anything that can exit () (including + m4_tmpfile), so that the atexit handler doesn't try to close + a garbage pointer as a file. */ + + selected_buffer = selected_diversion->u.buffer; + total_buffer_size -= selected_diversion->size; + selected_diversion->size = 0; + selected_diversion->u.file = NULL; + selected_diversion->u.file = m4_tmpfile (selected_diversion->divnum); + + if (selected_diversion->used > 0) + { + count = fwrite (selected_buffer, (size_t) selected_diversion->used, + 1, selected_diversion->u.file); + if (count != 1) + M4ERROR ((EXIT_FAILURE, errno, + "ERROR: cannot flush diversion to temporary file")); + } + + /* Reclaim the buffer space for other diversions. */ + + free (selected_buffer); + selected_diversion->used = 1; + } + + /* Reload output_file, just in case the flushed diversion was current. */ + + if (output_diversion == selected_diversion) + { + /* The flushed diversion was current indeed. */ + + output_file = output_diversion->u.file; + output_cursor = NULL; + output_unused = 0; + } + else + { + /* Close any selected file since it is not the current diversion. */ + if (selected_diversion) + { + FILE *file = selected_diversion->u.file; + selected_diversion->u.file = NULL; + if (m4_tmpclose (file, selected_diversion->divnum) != 0) + m4_error (0, errno, + _("cannot close temporary file for diversion")); + } + + /* The current buffer may be safely reallocated. */ + { + char *buffer = output_diversion->u.buffer; + output_diversion->u.buffer = xcharalloc ((size_t) wanted_size); + memcpy (output_diversion->u.buffer, buffer, output_diversion->used); + free (buffer); + } + + total_buffer_size += wanted_size - output_diversion->size; + output_diversion->size = wanted_size; + + output_cursor = output_diversion->u.buffer + output_diversion->used; + output_unused = wanted_size - output_diversion->used; + } +} + +/*--------------------------------------------------------------. +| Output one character CHAR, when it is known that it goes to a | +| diversion file or an in-memory diversion buffer. | +`--------------------------------------------------------------*/ + +#define OUTPUT_CHARACTER(Char) \ + if (output_file) \ + putc ((Char), output_file); \ + else if (output_unused == 0) \ + output_character_helper ((Char)); \ + else \ + (output_unused--, *output_cursor++ = (Char)) + +static void +output_character_helper (int character) +{ + make_room_for (1); + + if (output_file) + putc (character, output_file); + else + { + *output_cursor++ = character; + output_unused--; + } +} + +/*-------------------------------------------------------------------. +| Output one TEXT having LENGTH characters, when it is known that it | +| goes to a diversion file or an in-memory diversion buffer. | +`-------------------------------------------------------------------*/ + +void +output_text (const char *text, int length) +{ + int count; + + if (!output_diversion || !length) + return; + + if (!output_file && length > output_unused) + make_room_for (length); + + if (output_file) + { + count = fwrite (text, length, 1, output_file); + if (count != 1) + M4ERROR ((EXIT_FAILURE, errno, "ERROR: copying inserted file")); + } + else + { + memcpy (output_cursor, text, (size_t) length); + output_cursor += length; + output_unused -= length; + } +} + +/*--------------------------------------------------------------------. +| Add some text into an obstack OBS, taken from TEXT, having LENGTH | +| characters. If OBS is NULL, output the text to an external file | +| or an in-memory diversion buffer instead. If OBS is NULL, and | +| there is no output file, the text is discarded. LINE is the line | +| where the token starts (not necessarily current_line, in the case | +| of multiline tokens). | +| | +| If we are generating sync lines, the output has to be examined, | +| because we need to know how much output each input line generates. | +| In general, sync lines are output whenever a single input lines | +| generates several output lines, or when several input lines do not | +| generate any output. | +`--------------------------------------------------------------------*/ + +void +shipout_text (struct obstack *obs, const char *text, int length, int line) +{ + static bool start_of_output_line = true; + const char *cursor; + + /* If output goes to an obstack, merely add TEXT to it. */ + + if (obs != NULL) + { + obstack_grow (obs, text, length); + return; + } + + /* Do nothing if TEXT should be discarded. */ + + if (output_diversion == NULL) + return; + + /* Output TEXT to a file, or in-memory diversion buffer. */ + + if (!sync_output) + switch (length) + { + + /* In-line short texts. */ + + case 8: OUTPUT_CHARACTER (*text); text++; + case 7: OUTPUT_CHARACTER (*text); text++; + case 6: OUTPUT_CHARACTER (*text); text++; + case 5: OUTPUT_CHARACTER (*text); text++; + case 4: OUTPUT_CHARACTER (*text); text++; + case 3: OUTPUT_CHARACTER (*text); text++; + case 2: OUTPUT_CHARACTER (*text); text++; + case 1: OUTPUT_CHARACTER (*text); + case 0: + return; + + /* Optimize longer texts. */ + + default: + output_text (text, length); + } + else + { + /* Check for syncline only at the start of a token. Multiline + tokens, and tokens that are out of sync but in the middle of + the line, must wait until the next raw newline triggers a + syncline. */ + if (start_of_output_line) + { + start_of_output_line = false; + output_current_line++; +#ifdef DEBUG_OUTPUT + xfprintf (stderr, "DEBUG: line %d, cur %d, cur out %d\n", + line, current_line, output_current_line); +#endif + + /* Output a `#line NUM' synchronization directive if needed. + If output_current_line was previously given a negative + value (invalidated), output `#line NUM "FILE"' instead. */ + + if (output_current_line != line) + { + OUTPUT_CHARACTER ('#'); + OUTPUT_CHARACTER ('l'); + OUTPUT_CHARACTER ('i'); + OUTPUT_CHARACTER ('n'); + OUTPUT_CHARACTER ('e'); + OUTPUT_CHARACTER (' '); + for (cursor = ntoa (line, 10); *cursor; cursor++) + OUTPUT_CHARACTER (*cursor); + if (output_current_line < 1 && current_file[0] != '\0') + { + OUTPUT_CHARACTER (' '); + OUTPUT_CHARACTER ('"'); + for (cursor = current_file; *cursor; cursor++) + OUTPUT_CHARACTER (*cursor); + OUTPUT_CHARACTER ('"'); + } + OUTPUT_CHARACTER ('\n'); + output_current_line = line; + } + } + + /* Output the token, and track embedded newlines. */ + for (; length-- > 0; text++) + { + if (start_of_output_line) + { + start_of_output_line = false; + output_current_line++; +#ifdef DEBUG_OUTPUT + xfprintf (stderr, "DEBUG: line %d, cur %d, cur out %d\n", + line, current_line, output_current_line); +#endif + } + OUTPUT_CHARACTER (*text); + if (*text == '\n') + start_of_output_line = true; + } + } +} + +/* Functions for use by diversions. */ + +/*------------------------------------------------------------------. +| Make a file for diversion DIVNUM, and install it in the diversion | +| table. Grow the size of the diversion table as needed. | +`------------------------------------------------------------------*/ + +/* The number of possible diversions is limited only by memory and + available file descriptors (each overflowing diversion uses one). */ + +void +make_diversion (int divnum) +{ + m4_diversion *diversion = NULL; + + if (current_diversion == divnum) + return; + + if (output_diversion) + { + if (!output_diversion->size && !output_diversion->u.file) + { + assert (!output_diversion->used); + if (!gl_oset_remove (diversion_table, output_diversion)) + assert (false); + output_diversion->u.next = free_list; + free_list = output_diversion; + } + else if (output_diversion->size) + output_diversion->used = output_diversion->size - output_unused; + else if (output_diversion->used) + { + FILE *file = output_diversion->u.file; + output_diversion->u.file = NULL; + if (m4_tmpclose (file, output_diversion->divnum) != 0) + m4_error (0, errno, + _("cannot close temporary file for diversion")); + } + output_diversion = NULL; + output_file = NULL; + output_cursor = NULL; + output_unused = 0; + } + + current_diversion = divnum; + + if (divnum < 0) + return; + + if (divnum == 0) + diversion = &div0; + else + { + const void *elt; + if (gl_oset_search_atleast (diversion_table, threshold_diversion_CB, + &divnum, &elt)) + { + m4_diversion *temp = (m4_diversion *) elt; + if (temp->divnum == divnum) + diversion = temp; + } + } + if (diversion == NULL) + { + /* First time visiting this diversion. */ + if (free_list) + { + diversion = free_list; + free_list = diversion->u.next; + } + else + { + diversion = (m4_diversion *) obstack_alloc (&diversion_storage, + sizeof *diversion); + diversion->size = 0; + diversion->used = 0; + } + diversion->u.file = NULL; + diversion->divnum = divnum; + gl_oset_add (diversion_table, diversion); + } + + output_diversion = diversion; + if (output_diversion->size) + { + output_cursor = output_diversion->u.buffer + output_diversion->used; + output_unused = output_diversion->size - output_diversion->used; + } + else + { + if (!output_diversion->u.file && output_diversion->used) + output_diversion->u.file = m4_tmpopen (output_diversion->divnum, + false); + output_file = output_diversion->u.file; + } + output_current_line = -1; +} + +/*-------------------------------------------------------------------. +| Insert a FILE into the current output file, in the same manner | +| diversions are handled. This allows files to be included, without | +| having them rescanned by m4. | +`-------------------------------------------------------------------*/ + +void +insert_file (FILE *file) +{ + static char buffer[COPY_BUFFER_SIZE]; + size_t length; + + /* Optimize out inserting into a sink. */ + if (!output_diversion) + return; + + /* Insert output by big chunks. */ + while (1) + { + length = fread (buffer, 1, sizeof buffer, file); + if (ferror (file)) + M4ERROR ((EXIT_FAILURE, errno, "error reading inserted file")); + if (length == 0) + break; + output_text (buffer, length); + } +} + +/*-------------------------------------------------------------------. +| Insert DIVERSION (but not div0) into the current output file. The | +| diversion is NOT placed on the expansion obstack, because it must | +| not be rescanned. When the file is closed, it is deleted by the | +| system. | +`-------------------------------------------------------------------*/ + +static void +insert_diversion_helper (m4_diversion *diversion) +{ + /* Effectively undivert only if an output stream is active. */ + if (output_diversion) + { + if (diversion->size) + { + if (!output_diversion->u.file) + { + /* Transferring diversion metadata is faster than + copying contents. */ + assert (!output_diversion->used && output_diversion != &div0 + && !output_file); + output_diversion->u.buffer = diversion->u.buffer; + output_diversion->size = diversion->size; + output_cursor = diversion->u.buffer + diversion->used; + output_unused = diversion->size - diversion->used; + diversion->u.buffer = NULL; + } + else + { + /* Avoid double-charging the total in-memory size when + transferring from one in-memory diversion to + another. */ + total_buffer_size -= diversion->size; + output_text (diversion->u.buffer, diversion->used); + } + } + else if (!output_diversion->u.file) + { + /* Transferring diversion metadata is faster than copying + contents. */ + assert (!output_diversion->used && output_diversion != &div0 + && !output_file); + output_diversion->u.file = m4_tmprename (diversion->divnum, + output_diversion->divnum); + output_diversion->used = 1; + output_file = output_diversion->u.file; + diversion->u.file = NULL; + diversion->size = 1; + } + else + { + if (!diversion->u.file) + diversion->u.file = m4_tmpopen (diversion->divnum, true); + insert_file (diversion->u.file); + } + + output_current_line = -1; + } + + /* Return all space used by the diversion. */ + if (diversion->size) + { + if (!output_diversion) + total_buffer_size -= diversion->size; + free (diversion->u.buffer); + diversion->size = 0; + } + else + { + if (diversion->u.file) + { + FILE *file = diversion->u.file; + diversion->u.file = NULL; + if (m4_tmpclose (file, diversion->divnum) != 0) + m4_error (0, errno, + _("cannot clean temporary file for diversion")); + } + if (m4_tmpremove (diversion->divnum) != 0) + M4ERROR ((0, errno, "cannot clean temporary file for diversion")); + } + diversion->used = 0; + gl_oset_remove (diversion_table, diversion); + diversion->u.next = free_list; + free_list = diversion; +} + +/*------------------------------------------------------------------. +| Insert diversion number DIVNUM into the current output file. The | +| diversion is NOT placed on the expansion obstack, because it must | +| not be rescanned. When the file is closed, it is deleted by the | +| system. | +`------------------------------------------------------------------*/ + +void +insert_diversion (int divnum) +{ + const void *elt; + + /* Do not care about nonexistent diversions, and undiverting stdout + or self is a no-op. */ + if (divnum <= 0 || current_diversion == divnum) + return; + if (gl_oset_search_atleast (diversion_table, threshold_diversion_CB, + &divnum, &elt)) + { + m4_diversion *diversion = (m4_diversion *) elt; + if (diversion->divnum == divnum) + insert_diversion_helper (diversion); + } +} + +/*----------------------------------------------------------------. +| Get back all diversions. This is done just before exiting from | +| main, and from m4_undivert (), if called without arguments. | +`----------------------------------------------------------------*/ + +void +undivert_all (void) +{ + const void *elt; + gl_oset_iterator_t iter = gl_oset_iterator (diversion_table); + while (gl_oset_iterator_next (&iter, &elt)) + { + m4_diversion *diversion = (m4_diversion *) elt; + if (diversion->divnum != current_diversion) + insert_diversion_helper (diversion); + } + gl_oset_iterator_free (&iter); +} + +/*-------------------------------------------------------------. +| Produce all diversion information in frozen format on FILE. | +`-------------------------------------------------------------*/ + +void +freeze_diversions (FILE *file) +{ + int saved_number; + int last_inserted; + gl_oset_iterator_t iter; + const void *elt; + + saved_number = current_diversion; + last_inserted = 0; + make_diversion (0); + output_file = file; /* kludge in the frozen file */ + + iter = gl_oset_iterator (diversion_table); + while (gl_oset_iterator_next (&iter, &elt)) + { + m4_diversion *diversion = (m4_diversion *) elt; + if (diversion->size || diversion->used) + { + if (diversion->size) + xfprintf (file, "D%d,%d\n", diversion->divnum, diversion->used); + else + { + struct stat file_stat; + diversion->u.file = m4_tmpopen (diversion->divnum, true); + if (fstat (fileno (diversion->u.file), &file_stat) < 0) + M4ERROR ((EXIT_FAILURE, errno, "cannot stat diversion")); + if (file_stat.st_size < 0 + || (file_stat.st_size + 0UL + != (unsigned long int) file_stat.st_size)) + M4ERROR ((EXIT_FAILURE, 0, "diversion too large")); + xfprintf (file, "D%d,%lu\n", diversion->divnum, + (unsigned long int) file_stat.st_size); + } + + insert_diversion_helper (diversion); + putc ('\n', file); + + last_inserted = diversion->divnum; + } + } + gl_oset_iterator_free (&iter); + + /* Save the active diversion number, if not already. */ + + if (saved_number != last_inserted) + xfprintf (file, "D%d,0\n\n", saved_number); +} diff --git a/src/path.c b/src/path.c new file mode 100644 index 0000000..0e3c6e5 --- /dev/null +++ b/src/path.c @@ -0,0 +1,205 @@ +/* GNU m4 -- A simple macro processor + + Copyright (C) 1989-1993, 2004, 2006-2011 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/>. +*/ + +/* Handling of path search of included files via the builtins "include" + and "sinclude". */ + +#include "m4.h" + +struct includes +{ + struct includes *next; /* next directory to search */ + const char *dir; /* directory */ + int len; +}; + +typedef struct includes includes; + +static includes *dir_list; /* the list of path directories */ +static includes *dir_list_end; /* the end of same */ +static int dir_max_length; /* length of longest directory name */ + + +void +include_init (void) +{ + dir_list = NULL; + dir_list_end = NULL; + dir_max_length = 0; +} + +void +include_env_init (void) +{ + char *path; + char *path_end; + char *env_path; + + if (no_gnu_extensions) + return; + + env_path = getenv ("M4PATH"); + if (env_path == NULL) + return; + + env_path = xstrdup (env_path); + path = env_path; + + do + { + path_end = strchr (path, ':'); + if (path_end) + *path_end = '\0'; + add_include_directory (path); + path = path_end + 1; + } + while (path_end); + free (env_path); +} + +void +add_include_directory (const char *dir) +{ + includes *incl; + + if (no_gnu_extensions) + return; + + if (*dir == '\0') + dir = "."; + + incl = (includes *) xmalloc (sizeof (struct includes)); + incl->next = NULL; + incl->len = strlen (dir); + incl->dir = xstrdup (dir); + + if (incl->len > dir_max_length) /* remember len of longest directory */ + dir_max_length = incl->len; + + if (dir_list_end == NULL) + dir_list = incl; + else + dir_list_end->next = incl; + dir_list_end = incl; + +#ifdef DEBUG_INCL + xfprintf (stderr, "add_include_directory (%s);\n", dir); +#endif +} + +/* Attempt to open FILE; if it opens, verify that it is not a + directory, and ensure it does not leak across execs. */ +static FILE * +m4_fopen (const char *file) +{ + FILE *fp = fopen (file, "r"); + if (fp) + { + struct stat st; + int fd = fileno (fp); + if (fstat (fd, &st) == 0 && S_ISDIR (st.st_mode)) + { + fclose (fp); + errno = EISDIR; + return NULL; + } + if (set_cloexec_flag (fd, true) != 0) + M4ERROR ((warning_status, errno, + "Warning: cannot protect input file across forks")); + } + return fp; +} + +/* Search for FILE, first in `.', then according to -I options. If + successful, return the open file, and if RESULT is not NULL, set + *RESULT to a malloc'd string that represents the file found with + respect to the current working directory. */ + +FILE * +m4_path_search (const char *file, char **result) +{ + FILE *fp; + includes *incl; + char *name; /* buffer for constructed name */ + int e; + + if (result) + *result = NULL; + + /* Reject empty file. */ + if (!*file) + { + errno = ENOENT; + return NULL; + } + + /* Look in current working directory first. */ + fp = m4_fopen (file); + if (fp != NULL) + { + if (result) + *result = xstrdup (file); + return fp; + } + + /* If file not found, and filename absolute, fail. */ + if (IS_ABSOLUTE_FILE_NAME (file) || no_gnu_extensions) + return NULL; + e = errno; + + for (incl = dir_list; incl != NULL; incl = incl->next) + { + name = file_name_concat (incl->dir, file, NULL); + +#ifdef DEBUG_INCL + xfprintf (stderr, "m4_path_search (%s) -- trying %s\n", file, name); +#endif + + fp = m4_fopen (name); + if (fp != NULL) + { + if (debug_level & DEBUG_TRACE_PATH) + DEBUG_MESSAGE2 ("path search for `%s' found `%s'", file, name); + if (result) + *result = name; + else + free (name); + return fp; + } + free (name); + } + errno = e; + return fp; +} + +#ifdef DEBUG_INCL + +static void M4_GNUC_UNUSED +include_dump (void) +{ + includes *incl; + + xfprintf (stderr, "include_dump:\n"); + for (incl = dir_list; incl != NULL; incl = incl->next) + xfprintf (stderr, "\t%s\n", incl->dir); +} + +#endif /* DEBUG_INCL */ diff --git a/src/symtab.c b/src/symtab.c new file mode 100644 index 0000000..4965027 --- /dev/null +++ b/src/symtab.c @@ -0,0 +1,401 @@ +/* GNU m4 -- A simple macro processor + + Copyright (C) 1989-1994, 2003, 2006-2011 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/>. +*/ + +/* This file handles all the low level work around the symbol table. The + symbol table is a simple chained hash table. Each symbol is described + by a struct symbol, which is placed in the hash table based upon the + symbol name. Symbols that hash to the same entry in the table are + kept on a list, sorted by name. As a special case, to facilitate the + "pushdef" and "popdef" builtins, a symbol can be several times in the + symbol table, one for each definition. Since the name is the same, + all the entries for the symbol will be on the same list, and will + also, because the list is sorted, be adjacent. All the entries for a + name are simply ordered on the list by age. The current definition + will then always be the first found. */ + +#include "m4.h" +#include <limits.h> + +#ifdef DEBUG_SYM +/* When evaluating hash table performance, this profiling code shows + how many collisions were encountered. */ + +struct profile +{ + int entry; /* Number of times lookup_symbol called with this mode. */ + int comparisons; /* Number of times strcmp was called. */ + int misses; /* Number of times strcmp did not return 0. */ + long long bytes; /* Number of bytes compared. */ +}; + +static struct profile profiles[5]; +static symbol_lookup current_mode; + +/* On exit, show a profile of symbol table performance. */ +static void +show_profile (void) +{ + int i; + for (i = 0; i < 5; i++) + { + xfprintf(stderr, "m4: lookup mode %d called %d times, %d compares, " + "%d misses, %lld bytes\n", + i, profiles[i].entry, profiles[i].comparisons, + profiles[i].misses, profiles[i].bytes); + } +} + +/* Like strcmp (S1, S2), but also track profiling statistics. */ +static int +profile_strcmp (const char *s1, const char *s2) +{ + int i = 1; + int result; + while (*s1 && *s1 == *s2) + { + s1++; + s2++; + i++; + } + result = (unsigned char) *s1 - (unsigned char) *s2; + profiles[current_mode].comparisons++; + if (result != 0) + profiles[current_mode].misses++; + profiles[current_mode].bytes += i; + return result; +} + +# define strcmp profile_strcmp +#endif /* DEBUG_SYM */ + + +/*------------------------------------------------------------------. +| Initialise the symbol table, by allocating the necessary storage, | +| and zeroing all the entries. | +`------------------------------------------------------------------*/ + +/* Pointer to symbol table. */ +symbol **symtab; + +void +symtab_init (void) +{ + size_t i; + symbol **s; + + s = symtab = (symbol **) xnmalloc (hash_table_size, sizeof (symbol *)); + + for (i = 0; i < hash_table_size; i++) + s[i] = NULL; + +#ifdef DEBUG_SYM + { + int e = atexit(show_profile); + if (e != 0) + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: unable to show symtab profile")); + } +#endif /* DEBUG_SYM */ +} + +/*--------------------------------------------------. +| Return a hashvalue for a string, from GNU-emacs. | +`--------------------------------------------------*/ + +static size_t +hash (const char *s) +{ + register size_t val = 0; + + register const char *ptr = s; + register char ch; + + while ((ch = *ptr++) != '\0') + val = (val << 7) + (val >> (sizeof (val) * CHAR_BIT - 7)) + ch; + return val; +} + +/*--------------------------------------------. +| Free all storage associated with a symbol. | +`--------------------------------------------*/ + +void +free_symbol (symbol *sym) +{ + if (SYMBOL_PENDING_EXPANSIONS (sym) > 0) + SYMBOL_DELETED (sym) = true; + else + { + free (SYMBOL_NAME (sym)); + if (SYMBOL_TYPE (sym) == TOKEN_TEXT) + free (SYMBOL_TEXT (sym)); + free (sym); + } +} + +/*-------------------------------------------------------------------. +| Search in, and manipulation of the symbol table, are all done by | +| lookup_symbol (). It basically hashes NAME to a list in the | +| symbol table, and searches this list for the first occurrence of a | +| symbol with the name. | +| | +| The MODE parameter determines what lookup_symbol () will do. It | +| can either just do a lookup, do a lookup and insert if not | +| present, do an insertion even if the name is already in the list, | +| delete the first occurrence of the name on the list, or delete all | +| occurrences of the name on the list. | +`-------------------------------------------------------------------*/ + +symbol * +lookup_symbol (const char *name, symbol_lookup mode) +{ + size_t h; + int cmp = 1; + symbol *sym, *prev; + symbol **spp; + +#if DEBUG_SYM + current_mode = mode; + profiles[mode].entry++; +#endif /* DEBUG_SYM */ + + h = hash (name); + sym = symtab[h % hash_table_size]; + + for (prev = NULL; sym != NULL; prev = sym, sym = sym->next) + { + cmp = strcmp (SYMBOL_NAME (sym), name); + if (cmp >= 0) + break; + } + + /* If just searching, return status of search. */ + + if (mode == SYMBOL_LOOKUP) + return cmp == 0 ? sym : NULL; + + /* Symbol not found. */ + + spp = (prev != NULL) ? &prev->next : &symtab[h % hash_table_size]; + + switch (mode) + { + + case SYMBOL_INSERT: + + /* If the name was found in the table, check whether it is still in + use by a pending expansion. If so, replace the table element with + a new one; if not, just return the symbol. If not found, just + insert the name, and return the new symbol. */ + + if (cmp == 0 && sym != NULL) + { + if (SYMBOL_PENDING_EXPANSIONS (sym) > 0) + { + symbol *old = sym; + SYMBOL_DELETED (old) = true; + + sym = (symbol *) xmalloc (sizeof (symbol)); + SYMBOL_TYPE (sym) = TOKEN_VOID; + SYMBOL_TRACED (sym) = SYMBOL_TRACED (old); + SYMBOL_NAME (sym) = xstrdup (name); + SYMBOL_SHADOWED (sym) = false; + SYMBOL_MACRO_ARGS (sym) = false; + SYMBOL_BLIND_NO_ARGS (sym) = false; + SYMBOL_DELETED (sym) = false; + SYMBOL_PENDING_EXPANSIONS (sym) = 0; + + SYMBOL_NEXT (sym) = SYMBOL_NEXT (old); + SYMBOL_NEXT (old) = NULL; + (*spp) = sym; + } + return sym; + } + /* Fall through. */ + + case SYMBOL_PUSHDEF: + + /* Insert a name in the symbol table. If there is already a symbol + with the name, insert this in front of it, and mark the old + symbol as "shadowed". */ + + sym = (symbol *) xmalloc (sizeof (symbol)); + SYMBOL_TYPE (sym) = TOKEN_VOID; + SYMBOL_TRACED (sym) = false; + SYMBOL_NAME (sym) = xstrdup (name); + SYMBOL_SHADOWED (sym) = false; + SYMBOL_MACRO_ARGS (sym) = false; + SYMBOL_BLIND_NO_ARGS (sym) = false; + SYMBOL_DELETED (sym) = false; + SYMBOL_PENDING_EXPANSIONS (sym) = 0; + + SYMBOL_NEXT (sym) = *spp; + (*spp) = sym; + + if (mode == SYMBOL_PUSHDEF && cmp == 0) + { + SYMBOL_SHADOWED (SYMBOL_NEXT (sym)) = true; + SYMBOL_TRACED (sym) = SYMBOL_TRACED (SYMBOL_NEXT (sym)); + } + return sym; + + case SYMBOL_DELETE: + case SYMBOL_POPDEF: + + /* Delete occurrences of symbols with NAME. SYMBOL_DELETE kills + all definitions, SYMBOL_POPDEF kills only the first. + However, if the last instance of a symbol is marked for + tracing, reinsert a placeholder in the table. And if the + definition is still in use, let the caller free the memory + after it is done with the symbol. */ + + if (cmp != 0 || sym == NULL) + return NULL; + { + bool traced = false; + if (SYMBOL_NEXT (sym) != NULL + && SYMBOL_SHADOWED (SYMBOL_NEXT (sym)) + && mode == SYMBOL_POPDEF) + { + SYMBOL_SHADOWED (SYMBOL_NEXT (sym)) = false; + SYMBOL_TRACED (SYMBOL_NEXT (sym)) = SYMBOL_TRACED (sym); + } + else + traced = SYMBOL_TRACED (sym); + do + { + *spp = SYMBOL_NEXT (sym); + free_symbol (sym); + sym = *spp; + } + while (*spp != NULL && SYMBOL_SHADOWED (*spp) + && mode == SYMBOL_DELETE); + if (traced) + { + sym = (symbol *) xmalloc (sizeof (symbol)); + SYMBOL_TYPE (sym) = TOKEN_VOID; + SYMBOL_TRACED (sym) = true; + SYMBOL_NAME (sym) = xstrdup (name); + SYMBOL_SHADOWED (sym) = false; + SYMBOL_MACRO_ARGS (sym) = false; + SYMBOL_BLIND_NO_ARGS (sym) = false; + SYMBOL_DELETED (sym) = false; + SYMBOL_PENDING_EXPANSIONS (sym) = 0; + + SYMBOL_NEXT (sym) = *spp; + (*spp) = sym; + } + } + return NULL; + + case SYMBOL_LOOKUP: + default: + M4ERROR ((warning_status, 0, + "INTERNAL ERROR: invalid mode to symbol_lookup ()")); + abort (); + } +} + +/*-----------------------------------------------------------------. +| The following function is used for the cases where we want to do | +| something to each and every symbol in the table. The function | +| hack_all_symbols () traverses the symbol table, and calls a | +| specified function FUNC for each symbol in the table. FUNC is | +| called with a pointer to the symbol, and the DATA argument. | +| | +| FUNC may safely call lookup_symbol with mode SYMBOL_POPDEF or | +| SYMBOL_LOOKUP, but any other mode can break the iteration. | +`-----------------------------------------------------------------*/ + +void +hack_all_symbols (hack_symbol *func, void *data) +{ + size_t h; + symbol *sym; + symbol *next; + + for (h = 0; h < hash_table_size; h++) + { + /* We allow func to call SYMBOL_POPDEF, which can invalidate + sym, so we must grab the next element to traverse before + calling func. */ + for (sym = symtab[h]; sym != NULL; sym = next) + { + next = SYMBOL_NEXT (sym); + func (sym, data); + } + } +} + +#ifdef DEBUG_SYM + +static void symtab_print_list (int i); + +static void M4_GNUC_UNUSED +symtab_debug (void) +{ + token_data td; + const char *text; + symbol *s; + int delete; + static int i; + + while (next_token (&td, NULL) == TOKEN_WORD) + { + text = TOKEN_DATA_TEXT (&td); + if (*text == '_') + { + delete = 1; + text++; + } + else + delete = 0; + + s = lookup_symbol (text, SYMBOL_LOOKUP); + + if (s == NULL) + xprintf ("Name `%s' is unknown\n", text); + + lookup_symbol (text, delete ? SYMBOL_DELETE : SYMBOL_INSERT); + } + symtab_print_list (i++); +} + +static void +symtab_print_list (int i) +{ + symbol *sym; + size_t h; + + xprintf ("Symbol dump #%d:\n", i); + for (h = 0; h < hash_table_size; h++) + for (sym = symtab[h]; sym != NULL; sym = sym->next) + xprintf ("\tname %s, bucket %lu, addr %p, next %p, " + "flags%s%s%s, pending %d\n", + SYMBOL_NAME (sym), + (unsigned long int) h, sym, SYMBOL_NEXT (sym), + SYMBOL_TRACED (sym) ? " traced" : "", + SYMBOL_SHADOWED (sym) ? " shadowed" : "", + SYMBOL_DELETED (sym) ? " deleted" : "", + SYMBOL_PENDING_EXPANSIONS (sym)); +} + +#endif /* DEBUG_SYM */ |