m4-commit
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[SCM] GNU M4 source repository branch, argv_ref, updated. branch-cvs-rea


From: Eric Blake
Subject: [SCM] GNU M4 source repository branch, argv_ref, updated. branch-cvs-readonly-40-gd1b6a56
Date: Tue, 04 Dec 2007 23:38:07 +0000

This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GNU M4 source repository".

http://git.sv.gnu.org/gitweb/?p=m4.git;a=commitdiff;h=d1b6a563f559235700197ea87a9cb50accc43b95

The branch, argv_ref has been updated
       via  d1b6a563f559235700197ea87a9cb50accc43b95 (commit)
       via  8b898ea37cc61f2778881de0211836e2ba6d5e66 (commit)
      from  3b8c64664d1bf49b525206415d091f8ed562fecd (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit d1b6a563f559235700197ea87a9cb50accc43b95
Author: Eric Blake <address@hidden>
Date:   Mon Dec 3 11:53:45 2007 -0700

    Stage20: make m4wrap take builtins, add m4parw

commit 8b898ea37cc61f2778881de0211836e2ba6d5e66
Author: Eric Blake <address@hidden>
Date:   Wed Nov 21 10:14:28 2007 -0700

    Stage19: allow builtin tokens in more macros

-----------------------------------------------------------------------

Summary of changes:
 NEWS                 |   10 ++
 doc/m4.texinfo       |  223 +++++++++++++++++++++++++++++------
 examples/Makefile.am |    3 +-
 examples/wraplifo.m4 |   10 ++
 src/builtin.c        |  123 +++++++++++++++-----
 src/input.c          |  321 ++++++++++++++++++++++++++++++++++++++------------
 src/m4.h             |   16 ++-
 src/macro.c          |  127 ++++++++++++++------
 8 files changed, 648 insertions(+), 185 deletions(-)
 create mode 100644 examples/wraplifo.m4

diff --git a/NEWS b/NEWS
index eec8fdd..29589a1 100644
--- a/NEWS
+++ b/NEWS
@@ -15,11 +15,21 @@ Version 1.4.11 - ?? ??? 2007, by ????  (git version 
1.4.10a-*)
   possible to concatenate a builtin macro with anything else; a warning is
   now issued if this is attempted, although a future version of M4 may lift
   this restriction to match other implementations.
+* Fix the `m4wrap' builtin to accumulate wrapped text in FIFO order, as
+  required by POSIX.  Add a new builtin, `m4parw', for use in applications
+  that want to preserve LIFO order.
 * Several improvements in `index', `regexp', and `patsubst' builtins to
   speed up typical Autoconf usage.
 * Enhance the `ifelse' and `shift' builtins so that tail-recursive
   algorithms based on `$@' operate in linear, rather than quadratic, time
   and memory.
+* Enhance the `ifdef', `ifelse', `shift', and `m4wrap' builtins, as well as
+  all user macros, to transparently handle builtin tokens generated by
+  `defn'.
+* Enhance the `defn', `dumpdef', `ifdef', `popdef', and `undefine' macros
+  to warn when encountering a builtin token in the context of a macro name,
+  rather than acting on the empty string.  This was already done for
+  `define', `pushdef', `builtin', and `indir'.
 * A number of portability improvements inherited from gnulib.
 
 Version 1.4.10 - 09 Jul 2007, by Eric Blake  (CVS version 1.4.9c)
diff --git a/doc/m4.texinfo b/doc/m4.texinfo
index bdde3ed..67c73e7 100644
--- a/doc/m4.texinfo
+++ b/doc/m4.texinfo
@@ -2175,9 +2175,14 @@ defn([l], [r])
 
 @cindex builtins, special tokens
 @cindex tokens, builtin macro
-Using @code{defn} to generate special tokens for builtin macros outside
-of expected contexts can sometimes trigger warnings.  But most of the
-time, such tokens are silently converted to the empty string.
+Using @code{defn} to generate special tokens for builtin macros will
+generate a warning in contexts where a macro name is expected.  But in
+contexts that operate on text, the builtin token is just silently
+converted to an empty string.  As of M4 1.4.11, expansion of user macros
+will also preserve builtin tokens.  However, any use of builtin tokens
+outside of the second argument to @code{define} and @code{pushdef} is
+generally not portable, since earlier @acronym{GNU} M4 versions, as well
+as other @code{m4} implementations, vary on how such tokens are treated.
 
 @example
 defn(`defn')
@@ -2187,38 +2192,72 @@ define(defn(`divnum'), `cannot redefine a builtin 
token')
 @result{}
 divnum
 @result{}0
+len(defn(`divnum'))
address@hidden
 define(`echo', `$@@')
 @result{}
-define(`mydivnum', echo(defn(`divnum')))
+define(`mydivnum', shift(echo(`', defn(`divnum'))))
 @result{}
 mydivnum
address@hidden
+define(`', `empty-$1')
address@hidden
+defn(defn(`divnum'))
address@hidden:stdin:9: Warning: defn: invalid macro name ignored
address@hidden
+pushdef(defn(`divnum'), `oops')
address@hidden:stdin:10: Warning: pushdef: invalid macro name ignored
address@hidden
+indir(defn(`divnum'), `string')
address@hidden:stdin:11: Warning: indir: invalid macro name ignored
address@hidden
+indir(`', `string')
address@hidden
+popdef(defn(`divnum'))
address@hidden:stdin:13: Warning: popdef: invalid macro name ignored
address@hidden
+dumpdef(defn(`divnum'))
address@hidden:stdin:14: Warning: dumpdef: invalid macro name ignored
address@hidden
+undefine(defn(`divnum'))
address@hidden:stdin:15: Warning: undefine: invalid macro name ignored
address@hidden
+dumpdef(`')
address@hidden:@tabchar{}`empty-$1'
 @result{}
 @end example
 
-Also note that @code{defn} with multiple arguments can only join text
-macros, not builtins.  Likewise, when collecting macro arguments, a
-builtin token is preserved only when it occurs in isolation.  A future
-version of @acronym{GNU} M4 may lift these restrictions.
+Also note that as of M4 1.4.11, @code{defn} with multiple arguments can
+join text with builtin tokens.  However, when collecting macro
+arguments, a builtin token is preserved only when it occurs in
+isolation.  A future version of @acronym{GNU} M4 may lift this
+restriction.
 
 @example
 define(`a', `A')define(`AA', `b')
 @result{}
+traceon(`defn', `define')
address@hidden
 defn(`a', `divnum', `a')
address@hidden:stdin:2: Warning: defn: cannot concatenate builtin `divnum'
address@hidden: -1- defn(`a', `divnum', `a') -> ``A'<divnum>`A''
 @result{}AA
 define(`mydivnum', defn(`divnum', `divnum'))mydivnum
address@hidden:stdin:3: Warning: defn: cannot concatenate builtin `divnum'
address@hidden:stdin:3: Warning: defn: cannot concatenate builtin `divnum'
address@hidden
-define(`mydivnum', defn(`divnum')defn(`divnum'))mydivnum
address@hidden: -2- defn(`divnum', `divnum') -> `<divnum><divnum>'
 @error{}m4:stdin:4: Warning: define: cannot concatenate builtin `divnum'
 @error{}m4:stdin:4: Warning: define: cannot concatenate builtin `divnum'
address@hidden: -1- define(`mydivnum', `')
address@hidden
+traceoff(`defn', `define')
address@hidden
+define(`mydivnum', defn(`divnum')defn(`divnum'))mydivnum
address@hidden:stdin:6: Warning: define: cannot concatenate builtin `divnum'
address@hidden:stdin:6: Warning: define: cannot concatenate builtin `divnum'
 @result{}
 define(`mydivnum', defn(`divnum')`a')mydivnum
address@hidden:stdin:5: Warning: define: cannot concatenate builtin `divnum'
address@hidden:stdin:7: Warning: define: cannot concatenate builtin `divnum'
 @result{}A
 define(`mydivnum', `a'defn(`divnum'))mydivnum
address@hidden:stdin:6: Warning: define: cannot concatenate builtin `divnum'
address@hidden:stdin:8: Warning: define: cannot concatenate builtin `divnum'
 @result{}A
 @end example
 
@@ -2536,6 +2575,22 @@ ifdef(`no_such_macro', `yes', `no', `extra argument')
 @result{}no
 @end example
 
+As of M4 1.4.11, @code{ifdef} transparently handles builtin tokens
+generated by @code{defn} (@pxref{Defn}) that occur in either
address@hidden, although a warning is issued for invalid macro names.
+
address@hidden
+define(`', `empty')
address@hidden
+ifdef(defn(`defn'), `yes', `no')
address@hidden:stdin:2: Warning: ifdef: invalid macro name ignored
address@hidden
+define(`foo', ifdef(`divnum', defn(`divnum'), `undefined'))
address@hidden
+foo
address@hidden
address@hidden example
+
 @node Ifelse
 @section If-else construct, or multibranch
 
@@ -2636,6 +2691,24 @@ ifelse(`foo', `bar', `3', `gnu', `gnats', `6', `7', `8')
 @result{}7
 @end example
 
+As of M4 1.4.11, @code{ifelse} transparently handles builtin tokens
+generated by @code{defn} (@pxref{Defn}).  Because of this, it is always
+safe to compare two macro definitions, without worrying whether the
+macro might be a builtin.
+
address@hidden
+ifelse(defn(`defn'), `', `yes', `no')
address@hidden
+ifelse(defn(`defn'), defn(`divnum'), `yes', `no')
address@hidden
+ifelse(defn(`defn'), defn(`defn'), `yes', `no')
address@hidden
+define(`foo', ifelse(`', `', defn(`divnum')))
address@hidden
+foo
address@hidden
address@hidden example
+
 Naturally, the normal case will be slightly more advanced than these
 examples.  A common use of @code{ifelse} is in macros implementing loops
 of various kinds.
@@ -4132,12 +4205,19 @@ files.
 To save input text, use the builtin @code{m4wrap}:
 
 @deffn Builtin m4wrap (@var{string}, @dots{})
address@hidden Builtin m4parw (@var{string}, @dots{})
 Stores @var{string} in a safe place, to be reread when end of input is
 reached.  As a @acronym{GNU} extension, additional arguments are
 concatenated with a space to the @var{string}.
 
-The expansion of @code{m4wrap} is void.
-The macro @code{m4wrap} is recognized only with parameters.
+Successive invocations of @code{m4wrap} accumulate saved text in
+first-in, first-out order, as required by @acronym{POSIX}.  As a
address@hidden extension, the @code{m4parw} builtin accumulates text in
+last-in, first-out order.
+
+The expansion of @code{m4wrap} and @code{m4parw} is void.
+The macros @code{m4wrap} and @code{m4parw} are recognized only with
+parameters.
 @end deffn
 
 @example
@@ -4155,16 +4235,39 @@ This is the first and last normal input line.
 The saved input is only reread when the end of normal input is seen, and
 not if @code{m4exit} is used to exit @code{m4}.
 
address@hidden FIXME: this contradicts POSIX, which requires that "If the
address@hidden m4wrap macro is used multiple times, the arguments specified
address@hidden shall be processed in the order in which the m4wrap macros were
address@hidden processed."
-It is safe to call @code{m4wrap} from saved text, but then the order in
-which the saved text is reread is undefined.  If @code{m4wrap} is not used
-recursively, the saved pieces of text are reread in the opposite order
-in which they were saved (LIFO---last in, first out).  However, this
-behavior is likely to change in a future release, to match
address@hidden, so you should not depend on this order.
address@hidden @acronym{GNU} extensions
+It is safe to call @code{m4wrap} or @code{m4parw} from wrapped text,
+where all the recursively wrapped text is deferred until the current
+wrapped text is exhausted.  As of M4 1.4.11, when @code{m4wrap} is not
+used recursively, the saved pieces of text are reread in the same order
+in which they were saved (FIFO---first in, first out), as required by
address@hidden  However, earlier versions had reverse ordering
+(LIFO---last in, first out), so M4 1.4.11 also introduced @code{m4parw}
+to preserve this behavior.
+
address@hidden
+m4parw(`1
+')
address@hidden
+m4wrap(`2
+')
address@hidden
+m4parw(`3
+')
address@hidden
+m4parw(`4', `5
+')
address@hidden
+m4wrap(`6', `7
+')
address@hidden
+^D
address@hidden 5
address@hidden 7
address@hidden
address@hidden
address@hidden
address@hidden example
 
 Here is an example of implementing a factorial function using
 @code{m4wrap}:
@@ -4184,13 +4287,13 @@ Invocations of @code{m4wrap} at the same recursion 
level are
 concatenated and rescanned as usual:
 
 @example
-define(`aa', `AA
+define(`ab', `AB
 ')
 @result{}
-m4wrap(`a')m4wrap(`a')
+m4wrap(`a')m4wrap(`b')
 @result{}
 ^D
address@hidden
address@hidden
 @end example
 
 @noindent
@@ -4205,6 +4308,24 @@ m4wrap(`m4wrap(`)')len(abc')
 @error{}m4:stdin:1: len: end of file in argument list
 @end example
 
+As of M4 1.4.11, @code{m4wrap} transparently handles builtin tokens
+generated by @code{defn} (@pxref{Defn}).  However, for portability, it
+is better to defer the evaluation of @code{defn} along with the rest of
+the wrapped text, as is done for @code{foo} in the example below, rather
+than computing the builtin token up front, as is done for @code{bar}.
+
address@hidden
+m4wrap(`define(`foo', defn(`divnum'))foo
+')
address@hidden
+m4wrap(`define(`bar', ')m4wrap(defn(`divnum'))m4wrap(`)bar
+')
address@hidden
+^D
address@hidden
address@hidden
address@hidden example
+
 @node File Inclusion
 @chapter File inclusion
 
@@ -6045,9 +6166,13 @@ __line__
 @result{}8
 __line__
 @result{}11
+m4wrap(`__line__
+')
address@hidden
 ^D
 @result{}6
 @result{}6
address@hidden
 @end example
 
 The @address@hidden macro behaves like @samp{$0} in shell
@@ -6489,15 +6614,17 @@ of @samp{-} on the command line.
 
 @item
 @acronym{POSIX} requires @code{m4wrap} (@pxref{M4wrap}) to act in FIFO
-(first-in, first-out) order, but @acronym{GNU} @code{m4} currently uses
+(first-in, first-out) order, and most other implementations obey this.
+However, versions of @acronym{GNU} @code{m4} earlier than 1.4.11 used
 LIFO order.  Furthermore, @acronym{POSIX} states that only the first
-argument to @code{m4wrap} is saved for later evaluation, bug
+argument to @code{m4wrap} is saved for later evaluation, but
 @acronym{GNU} @code{m4} saves and processes all arguments, with output
 separated by spaces.
 
-However, it is possible to emulate @acronym{POSIX} behavior by
-including the file @address@hidden/@/examples/@/wrapfifo.m4}
-from the distribution:
+It is possible to emulate @acronym{POSIX} behavior even with older
+versions of @acronym{GNU} M4 by including the file
address@hidden@value{VERSION}/@/examples/@/wrapfifo.m4} from the
+distribution:
 
 @example
 undivert(`wrapfifo.m4')dnl
@@ -6520,6 +6647,32 @@ m4wrap(`a`'m4wrap(`c
 @result{}abc
 @end example
 
+It is likewise possible to emulate LIFO behavior without resorting to
+the @acronym{GNU} M4 extensions of @code{m4parw} or @code{builtin}, by
+including the file @address@hidden/@/examples/@/wraplifo.m4}
+from the distribution:
+
address@hidden
+undivert(`wraplifo.m4')dnl
address@hidden Redefine m4wrap to have LIFO semantics.
address@hidden(`_m4wrap_level', `0')dnl
address@hidden(`_m4wrap', defn(`m4wrap'))dnl
address@hidden(`m4wrap',
address@hidden(`m4wrap'_m4wrap_level,
address@hidden       `define(`m4wrap'_m4wrap_level,
address@hidden               `$1'defn(`m4wrap'_m4wrap_level))',
address@hidden       `_m4wrap(`define(`_m4wrap_level', incr(_m4wrap_level))dnl
address@hidden'_m4wrap_level)dnl
address@hidden(`m4wrap'_m4wrap_level, `$1')')')dnl
+include(`wraplifo.m4')
address@hidden
+m4wrap(`a`'m4wrap(`c
+', `d')')m4wrap(`b')
address@hidden
+^D
address@hidden
address@hidden example
+
 @item
 @acronym{POSIX} states that builtins that require arguments, but are
 called without arguments, have undefined behavior.  Traditional
diff --git a/examples/Makefile.am b/examples/Makefile.am
index b1ef68a..c1dc522 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -58,4 +58,5 @@ translit.m4 \
 undivert.incl \
 undivert.m4 \
 wrap.m4 \
-wrapfifo.m4
+wrapfifo.m4 \
+wraplifo.m4
diff --git a/examples/wraplifo.m4 b/examples/wraplifo.m4
new file mode 100644
index 0000000..bdbf3fb
--- /dev/null
+++ b/examples/wraplifo.m4
@@ -0,0 +1,10 @@
+dnl Redefine m4wrap to have LIFO semantics.
+define(`_m4wrap_level', `0')dnl
+define(`_m4wrap', defn(`m4wrap'))dnl
+define(`m4wrap',
+`ifdef(`m4wrap'_m4wrap_level,
+       `define(`m4wrap'_m4wrap_level,
+               `$1'defn(`m4wrap'_m4wrap_level))',
+       `_m4wrap(`define(`_m4wrap_level', incr(_m4wrap_level))dnl
+m4wrap'_m4wrap_level)dnl
+define(`m4wrap'_m4wrap_level, `$1')')')dnl
diff --git a/src/builtin.c b/src/builtin.c
index e7b5282..7801d9c 100644
--- a/src/builtin.c
+++ b/src/builtin.c
@@ -71,6 +71,7 @@ DECLARE (m4_index);
 DECLARE (m4_indir);
 DECLARE (m4_len);
 DECLARE (m4_m4exit);
+DECLARE (m4_m4parw);
 DECLARE (m4_m4wrap);
 DECLARE (m4_maketemp);
 DECLARE (m4_mkstemp);
@@ -110,31 +111,32 @@ builtin_tab[] =
   { "debugfile",       true,   false,  false,  m4_debugfile },
   { "decr",            false,  false,  true,   m4_decr },
   { "define",          false,  true,   true,   m4_define },
-  { "defn",            false,  false,  true,   m4_defn },
+  { "defn",            false,  true,   true,   m4_defn },
   { "divert",          false,  false,  false,  m4_divert },
   { "divnum",          false,  false,  false,  m4_divnum },
   { "dnl",             false,  false,  false,  m4_dnl },
-  { "dumpdef",         false,  false,  false,  m4_dumpdef },
+  { "dumpdef",         false,  true,   false,  m4_dumpdef },
   { "errprint",                false,  false,  true,   m4_errprint },
   { "esyscmd",         true,   false,  true,   m4_esyscmd },
   { "eval",            false,  false,  true,   m4_eval },
   { "format",          true,   false,  true,   m4_format },
-  { "ifdef",           false,  false,  true,   m4_ifdef },
-  { "ifelse",          false,  false,  true,   m4_ifelse },
+  { "ifdef",           false,  true,   true,   m4_ifdef },
+  { "ifelse",          false,  true,   true,   m4_ifelse },
   { "include",         false,  false,  true,   m4_include },
   { "incr",            false,  false,  true,   m4_incr },
   { "index",           false,  false,  true,   m4_index },
   { "indir",           true,   true,   true,   m4_indir },
   { "len",             false,  false,  true,   m4_len },
   { "m4exit",          false,  false,  false,  m4_m4exit },
-  { "m4wrap",          false,  false,  true,   m4_m4wrap },
+  { "m4parw",          true,   true,   true,   m4_m4parw },
+  { "m4wrap",          false,  true,   true,   m4_m4wrap },
   { "maketemp",                false,  false,  true,   m4_maketemp },
   { "mkstemp",         false,  false,  true,   m4_mkstemp },
   { "patsubst",                true,   false,  true,   m4_patsubst },
-  { "popdef",          false,  false,  true,   m4_popdef },
+  { "popdef",          false,  true,   true,   m4_popdef },
   { "pushdef",         false,  true,   true,   m4_pushdef },
   { "regexp",          true,   false,  true,   m4_regexp },
-  { "shift",           false,  false,  true,   m4_shift },
+  { "shift",           false,  true,   true,   m4_shift },
   { "sinclude",                false,  false,  true,   m4_sinclude },
   { "substr",          false,  false,  true,   m4_substr },
   { "syscmd",          false,  false,  true,   m4_syscmd },
@@ -142,7 +144,7 @@ builtin_tab[] =
   { "traceoff",                false,  false,  false,  m4_traceoff },
   { "traceon",         false,  false,  false,  m4_traceon },
   { "translit",                false,  false,  true,   m4_translit },
-  { "undefine",                false,  false,  true,   m4_undefine },
+  { "undefine",                false,  true,   true,   m4_undefine },
   { "undivert",                false,  false,  false,  m4_undivert },
 
   { 0,                 false,  false,  false,  0 },
@@ -441,6 +443,7 @@ define_user_macro (const char *name, size_t len, const char 
*text,
 
   SYMBOL_TYPE (s) = TOKEN_TEXT;
   SYMBOL_TEXT (s) = defn;
+  SYMBOL_MACRO_ARGS (s) = true;
 
   /* Implement --warn-macro-sequence.  */
   if (macro_sequence_inuse && text)
@@ -694,11 +697,15 @@ m4_define (struct obstack *obs, int argc, macro_arguments 
*argv)
 static void
 m4_undefine (struct obstack *obs, int argc, macro_arguments *argv)
 {
+  const char *me = ARG (0);
   int i;
-  if (bad_argc (ARG (0), argc, 1, -1))
+  if (bad_argc (me, argc, 1, -1))
     return;
   for (i = 1; i < argc; i++)
-    lookup_symbol (ARG (i), SYMBOL_DELETE);
+    if (arg_type (argv, i) != TOKEN_TEXT)
+      m4_warn (0, me, _("invalid macro name ignored"));
+    else
+      lookup_symbol (ARG (i), SYMBOL_DELETE);
 }
 
 static void
@@ -710,11 +717,15 @@ m4_pushdef (struct obstack *obs, int argc, 
macro_arguments *argv)
 static void
 m4_popdef (struct obstack *obs, int argc, macro_arguments *argv)
 {
+  const char *me = ARG (0);
   int i;
-  if (bad_argc (ARG (0), argc, 1, -1))
+  if (bad_argc (me, argc, 1, -1))
     return;
   for (i = 1; i < argc; i++)
-    lookup_symbol (ARG (i), SYMBOL_POPDEF);
+    if (arg_type (argv, i) != TOKEN_TEXT)
+      m4_warn (0, me, _("invalid macro name ignored"));
+    else
+      lookup_symbol (ARG (i), SYMBOL_POPDEF);
 }
 
 /*---------------------.
@@ -724,10 +735,17 @@ m4_popdef (struct obstack *obs, int argc, macro_arguments 
*argv)
 static void
 m4_ifdef (struct obstack *obs, int argc, macro_arguments *argv)
 {
+  const char *me = ARG (0);
   symbol *s;
 
-  if (bad_argc (ARG (0), argc, 2, 3))
+  if (bad_argc (me, argc, 2, 3))
     return;
+  if (arg_type (argv, 1) != TOKEN_TEXT)
+    {
+      m4_warn (0, me, _("invalid macro name ignored"));
+      push_arg (obs, argv, 3);
+      return;
+    }
   s = lookup_symbol (ARG (1), SYMBOL_LOOKUP);
   push_arg (obs, argv, (s && SYMBOL_TYPE (s) != TOKEN_VOID) ? 2 : 3);
 }
@@ -839,6 +857,11 @@ m4_dumpdef (struct obstack *obs, int argc, macro_arguments 
*argv)
     {
       for (i = 1; i < argc; i++)
        {
+         if (arg_type (argv, i) != TOKEN_TEXT)
+           {
+             m4_warn (0, me, _("invalid macro name ignored"));
+             continue;
+           }
          s = lookup_symbol (ARG (i), SYMBOL_LOOKUP);
          if (s != NULL && SYMBOL_TYPE (s) != TOKEN_VOID)
            dump_symbol (s, &data);
@@ -973,6 +996,11 @@ m4_defn (struct obstack *obs, int argc, macro_arguments 
*argv)
 
   for (i = 1; i < argc; i++)
     {
+      if (arg_type (argv, i) != TOKEN_TEXT)
+       {
+         m4_warn (0, me, _("invalid macro name ignored"));
+         continue;
+       }
       s = lookup_symbol (ARG (i), SYMBOL_LOOKUP);
       if (s == NULL)
        continue;
@@ -991,10 +1019,8 @@ m4_defn (struct obstack *obs, int argc, macro_arguments 
*argv)
            m4_warn (0, me,
                     _("builtin `%s' requested by frozen file not found"),
                     ARG (i));
-         else if (argc != 2)
-           m4_warn (0, me, _("cannot concatenate builtin `%s'"), ARG (i));
          else
-           push_macro (b);
+           push_macro (obs, b);
          break;
 
        default:
@@ -1294,10 +1320,10 @@ m4_dnl (struct obstack *obs, int argc, macro_arguments 
*argv)
   skip_line (me);
 }
 
-/*-------------------------------------------------------------------------.
-| Shift all argument one to the left, discarding the first argument.  Each |
-| output argument is quoted with the current quotes.                      |
-`-------------------------------------------------------------------------*/
+/*--------------------------------------------------------------------.
+| Shift all arguments one to the left, discarding the first          |
+| argument.  Each output argument is quoted with the current quotes.  |
+`--------------------------------------------------------------------*/
 
 static void
 m4_shift (struct obstack *obs, int argc, macro_arguments *argv)
@@ -1580,25 +1606,62 @@ m4_m4exit (struct obstack *obs, int argc, 
macro_arguments *argv)
   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.                                                 |
-`-------------------------------------------------------------------------*/
+/*-------------------------------------------------------------------.
+| Save the argument text until EOF has been seen, allowing for user  |
+| specified cleanup action.  FIFO determines whether text is         |
+| appended (per POSIX) or prepended (per older versions of GNU M4).  |
+| When obeying POSIX, save only the first argument; otherwise        |
+| concatenate additional arguments with a space.                     |
+`-------------------------------------------------------------------*/
 
 static void
-m4_m4wrap (struct obstack *obs, int argc, macro_arguments *argv)
+m4_wrap_helper (int argc, macro_arguments *argv, bool fifo)
 {
+  int i;
+  struct obstack *obs;
+
   if (bad_argc (ARG (0), argc, 1, -1))
     return;
-  obs = push_wrapup_init ();
-  if (no_gnu_extensions)
+  obs = push_wrapup_init (fifo);
+  if (arg_type (argv, 1) == TOKEN_TEXT)
     obstack_grow (obs, ARG (1), arg_len (argv, 1));
   else
-    // TODO - allow builtins, rather than always flattening
-    arg_print (obs, argv, 1, NULL, true, false, " ", NULL);
+    push_macro (obs, arg_func (argv, 1));
+  if (!no_gnu_extensions)
+    for (i = 2; i < argc; i++)
+      {
+       obstack_1grow (obs, ' ');
+       if (arg_type (argv, i) == TOKEN_TEXT)
+         obstack_grow (obs, ARG (i), arg_len (argv, i));
+       else
+         push_macro (obs, arg_func (argv, i));
+      }
   push_wrapup_finish ();
 }
+
+/*-----------------------------------------------------------------.
+| Save the argument text in FIFO order until EOF has been seen,    |
+| allowing for user specified cleanup action.  Extra arguments are |
+| saved when not in POSIX mode.                                    |
+`-----------------------------------------------------------------*/
+
+static void
+m4_m4wrap (struct obstack *obs, int argc, macro_arguments *argv)
+{
+  m4_wrap_helper (argc, argv, true);
+}
+
+/*-----------------------------------------------------------------.
+| Save the argument text in LIFO order until EOF has been seen,    |
+| allowing for user specified cleanup action.  Only available as a |
+| GNU extension.                                                   |
+`-----------------------------------------------------------------*/
+
+static void
+m4_m4parw (struct obstack *obs, int argc, macro_arguments *argv)
+{
+  m4_wrap_helper (argc, argv, false);
+}
 
 /* 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
diff --git a/src/input.c b/src/input.c
index d1ddf25..94a427d 100644
--- a/src/input.c
+++ b/src/input.c
@@ -70,10 +70,9 @@
 
 enum input_type
 {
-  INPUT_STRING,                /* String resulting from macro expansion.  */
-  INPUT_FILE,          /* File from command line or include.  */
-  INPUT_MACRO,         /* Builtin resulting from defn.  */
-  INPUT_CHAIN          /* FIFO chain of separate strings and $@ refs.  */
+  INPUT_STRING,        /* String resulting from macro expansion.  */
+  INPUT_FILE,  /* File from command line or include.  */
+  INPUT_CHAIN  /* FIFO chain of separate strings, builtins, and $@ refs.  */
 };
 
 /* Using an enum as a bitfield is not portable.  For nicer debugging,
@@ -108,7 +107,6 @@ struct input_block
          bool_bitfield advance : 1; /* track previous start_of_input_line */
        }
        u_f;    /* INPUT_FILE */
-      builtin_func *func;      /* INPUT_MACRO */
       struct
        {
          token_chain *chain;   /* current link in chain */
@@ -237,33 +235,65 @@ push_file (FILE *fp, const char *title, bool close)
   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)
+/*------------------------------------------------------------------.
+| Given an obstack OBS, capture any unfinished text as a link, then |
+| append the builtin FUNC as the next link in the input chain that  |
+| starts at *START and ends at *END.                               |
+`------------------------------------------------------------------*/
+static void
+append_macro (struct obstack *obs, builtin_func *func, token_chain **start,
+             token_chain **end)
 {
-  input_block *i;
+  token_chain *chain;
+  size_t len = obstack_object_size (obs);
 
-  if (next != NULL)
+  if (len)
     {
-      obstack_free (current_input, next);
-      next = NULL;
+      char *str = (char *) obstack_finish (obs);
+      chain = (token_chain *) obstack_alloc (obs, sizeof *chain);
+      if (*end)
+       (*end)->next = chain;
+      else
+       *start = chain;
+      *end = chain;
+      chain->next = NULL;
+      chain->type = CHAIN_STR;
+      chain->quote_age = 0;
+      chain->u.u_s.str = str;
+      chain->u.u_s.len = len;
+      chain->u.u_s.level = -1;
     }
 
-  i = (input_block *) obstack_alloc (current_input, sizeof *i);
-  i->type = INPUT_MACRO;
-  i->cache = false;
-  i->file = current_file;
-  i->line = current_line;
-  input_change = true;
+  chain = (token_chain *) obstack_alloc (obs, sizeof *chain);
+  if (*end)
+    (*end)->next = chain;
+  else
+    *start = chain;
+  *end = chain;
+  chain->next = NULL;
+  chain->type = CHAIN_FUNC;
+  chain->quote_age = 0;
+  chain->u.func = func;
+}
 
-  i->u.func = func;
-  i->prev = isp;
-  isp = i;
+/*-------------------------------------------------------------------.
+| push_macro () pushes a builtin macro's definition onto the obstack |
+| OBS, which is either the input or wrapup stack.                   |
+`-------------------------------------------------------------------*/
+
+void
+push_macro (struct obstack *obs, builtin_func *func)
+{
+  input_block *block = (obs == current_input ? next : wsp);
+  assert (block);
+  if (block->type == INPUT_STRING)
+    {
+      block->type = INPUT_CHAIN;
+      block->u.u_c.chain = block->u.u_c.end = NULL;
+    }
+  else
+    assert (block->type == INPUT_CHAIN);
+  append_macro (obs, func, &block->u.u_c.chain, &block->u.u_c.end);
 }
 
 /*------------------------------------------------------------------.
@@ -327,6 +357,17 @@ push_token (token_data *token, int level, bool inuse)
          return false;
        }
     }
+  else if (TOKEN_DATA_TYPE (token) == TOKEN_FUNC)
+    {
+      if (next->type == INPUT_STRING)
+       {
+         next->type = INPUT_CHAIN;
+         next->u.u_c.chain = next->u.u_c.end = NULL;
+       }
+      append_macro (current_input, TOKEN_DATA_FUNC (token), &next->u.u_c.chain,
+                   &next->u.u_c.end);
+      return false;
+    }
   else
     {
       /* For composite tokens, if argv is already in use, creating
@@ -391,6 +432,13 @@ push_token (token_data *token, int level, bool inuse)
     }
   while (src_chain)
     {
+      if (src_chain->type == CHAIN_FUNC)
+       {
+         append_macro (current_input, src_chain->u.func, &next->u.u_c.chain,
+                       &next->u.u_c.end);
+         src_chain = src_chain->next;
+         continue;
+       }
       if (level == -1)
        {
          /* Nothing to copy, since link already lives on obstack.  */
@@ -507,20 +555,57 @@ push_string_finish (void)
 /*--------------------------------------------------------------.
 | The function push_wrapup_init () returns an obstack ready for |
 | direct expansion of wrapup text, and should be followed by    |
-| push_wrapup_finish ().                                        |
+| push_wrapup_finish ().  If FIFO, then wrap new text after any |
+| previously wrapped text.                                      |
 `--------------------------------------------------------------*/
 
 struct obstack *
-push_wrapup_init (void)
+push_wrapup_init (bool fifo)
 {
   input_block *i;
-  i = (input_block *) obstack_alloc (wrapup_stack, sizeof *i);
-  i->prev = wsp;
-  i->type = INPUT_STRING;
-  i->cache = false;
-  i->file = current_file;
-  i->line = current_line;
-  wsp = i;
+  token_chain *chain;
+
+  assert (obstack_object_size (wrapup_stack) == 0);
+  if (fifo && wsp)
+    {
+      i = wsp;
+      if (i->type == INPUT_STRING)
+       {
+         chain = (token_chain *) obstack_alloc (wrapup_stack, sizeof *chain);
+         chain->next = NULL;
+         chain->type = CHAIN_STR;
+         chain->quote_age = 0;
+         chain->u.u_s.str = i->u.u_s.string;
+         chain->u.u_s.len = i->u.u_s.len;
+         chain->u.u_s.level = -1;
+         i->type = INPUT_CHAIN;
+         i->u.u_c.chain = i->u.u_c.end = chain;
+       }
+      assert (i->type == INPUT_CHAIN);
+      if (i->u.u_c.end->type == CHAIN_LOC)
+       chain = i->u.u_c.end;
+      else
+       {
+         chain = (token_chain *) obstack_alloc (wrapup_stack, sizeof *chain);
+         i->u.u_c.end->next = chain;
+         i->u.u_c.end = chain;
+         chain->next = NULL;
+         chain->type = CHAIN_LOC;
+         chain->quote_age = 0;
+       }
+      chain->u.u_l.file = current_file;
+      chain->u.u_l.line = current_line;
+    }
+  else
+    {
+      i = (input_block *) obstack_alloc (wrapup_stack, sizeof *i);
+      i->prev = wsp;
+      i->type = INPUT_STRING;
+      i->cache = false;
+      i->file = current_file;
+      i->line = current_line;
+      wsp = i;
+    }
   return wrapup_stack;
 }
 
@@ -532,15 +617,40 @@ void
 push_wrapup_finish (void)
 {
   input_block *i = wsp;
-  if (obstack_object_size (wrapup_stack) == 0)
+  size_t len = obstack_object_size (wrapup_stack);
+  char *str;
+
+  if (len)
+    str = (char *) obstack_finish (wrapup_stack);
+  if (str || i->type == INPUT_CHAIN)
     {
-      wsp = i->prev;
-      obstack_free (wrapup_stack, i);
+      if (i->type == INPUT_STRING)
+       {
+         i->u.u_s.string = str;
+         i->u.u_s.len = len;
+       }
+      else
+       {
+         if (str)
+           {
+             token_chain *chain
+               = (token_chain *) obstack_alloc (wrapup_stack, sizeof *chain);
+             assert (i->u.u_c.end);
+             i->u.u_c.end->next = chain;
+             i->u.u_c.end = chain;
+             chain->next = NULL;
+             chain->type = CHAIN_STR;
+             chain->quote_age = 0;
+             chain->u.u_s.str = str;
+             chain->u.u_s.len = len;
+             chain->u.u_s.level = -1;
+           }
+       }
     }
   else
     {
-      i->u.u_s.len = obstack_object_size (wrapup_stack);
-      i->u.u_s.string = (char *) obstack_finish (wrapup_stack);
+      wsp = i->prev;
+      obstack_free (wrapup_stack, i);
     }
 }
 
@@ -568,11 +678,6 @@ pop_input (bool cleanup)
        return false;
       break;
 
-    case INPUT_MACRO:
-      if (!cleanup)
-       return false;
-      break;
-
     case INPUT_CHAIN:
       chain = isp->u.u_c.chain;
       assert (!chain || !cleanup);
@@ -586,12 +691,18 @@ pop_input (bool cleanup)
              if (chain->u.u_s.level >= 0)
                adjust_refcount (chain->u.u_s.level, false);
              break;
+           case CHAIN_FUNC:
+              if (chain->u.func)
+                return false;
+              break;
            case CHAIN_ARGV:
              if (chain->u.u_a.comma
                  || chain->u.u_a.index < arg_argc (chain->u.u_a.argv))
                return false;
              arg_adjust_refcount (chain->u.u_a.argv, false);
              break;
+           case CHAIN_LOC:
+             return false;
            default:
              assert (!"pop_input");
              abort ();
@@ -693,9 +804,6 @@ input_print (struct obstack *obs, const input_block *input)
       obstack_grow (obs, input->file, strlen (input->file));
       obstack_1grow (obs, '>');
       break;
-    case INPUT_MACRO:
-      func_print (obs, find_builtin_by_addr (input->u.func), false, NULL);
-      break;
     case INPUT_CHAIN:
       {
        token_chain *chain = input->u.u_c.chain;
@@ -708,6 +816,10 @@ input_print (struct obstack *obs, const input_block *input)
                                   &len))
                  return;
                break;
+             case CHAIN_FUNC:
+               func_print (obs, find_builtin_by_addr (chain->u.func), false,
+                           NULL);
+               break;
              case CHAIN_ARGV:
                if (arg_print (obs, chain->u.u_a.argv, chain->u.u_a.index,
                               quote_cache (NULL, chain->quote_age,
@@ -769,9 +881,6 @@ peek_input (bool allow_argv)
          block->u.u_f.end = true;
          break;
 
-       case INPUT_MACRO:
-         return CHAR_MACRO;
-
        case INPUT_CHAIN:
          chain = block->u.u_c.chain;
          while (chain)
@@ -783,6 +892,10 @@ peek_input (bool allow_argv)
                  if (chain->u.u_s.len)
                    return to_uchar (*chain->u.u_s.str);
                  break;
+               case CHAIN_FUNC:
+                 if (chain->u.func)
+                   return CHAR_MACRO;
+                 break;
                case CHAIN_ARGV:
                  /* Rather than directly parse argv here, we push
                     another input block containing the next unparsed
@@ -809,6 +922,8 @@ peek_input (bool allow_argv)
                      return peek_input (allow_argv);
                    }
                  break;
+               case CHAIN_LOC:
+                 break;
                default:
                  assert (!"peek_input");
                  abort ();
@@ -892,11 +1007,6 @@ next_char_1 (bool allow_quote)
            }
          break;
 
-       case INPUT_MACRO:
-         /* INPUT_MACRO input sources has only one token */
-         pop_input (true);
-         return CHAR_MACRO;
-
        case INPUT_CHAIN:
          chain = isp->u.u_c.chain;
          while (chain)
@@ -917,6 +1027,10 @@ next_char_1 (bool allow_quote)
                  if (chain->u.u_s.level >= 0)
                    adjust_refcount (chain->u.u_s.level, false);
                  break;
+               case CHAIN_FUNC:
+                 if (chain->u.func)
+                   return CHAR_MACRO;
+                 break;
                case CHAIN_ARGV:
                  /* Rather than directly parse argv here, we push
                     another input block containing the next unparsed
@@ -941,6 +1055,12 @@ next_char_1 (bool allow_quote)
                    }
                  arg_adjust_refcount (chain->u.u_a.argv, false);
                  break;
+               case CHAIN_LOC:
+                 isp->file = chain->u.u_l.file;
+                 isp->line = chain->u.u_l.line;
+                 input_change = true;
+                 isp->u.u_c.chain = chain->next;
+                 return next_char_1 (allow_quote);
                default:
                  assert (!"next_char_1");
                  abort ();
@@ -987,17 +1107,24 @@ skip_line (const char *name)
     input_change = true;
 }
 
-/*-------------------------------------------------------------------.
-| When a MACRO token is seen, next_token () uses init_macro_token () |
-| to retrieve the value of the function pointer.                     |
-`-------------------------------------------------------------------*/
+/*--------------------------------------------------------------.
+| When a next_token() sees a MACRO token with peek_input, this  |
+| retrieves the value of the function pointer, and consumes the |
+| input so the caller does not need to do next_char.            |
+`--------------------------------------------------------------*/
 
 static void
 init_macro_token (token_data *td)
 {
-  assert (isp->type == INPUT_MACRO);
   TOKEN_DATA_TYPE (td) = TOKEN_FUNC;
-  TOKEN_DATA_FUNC (td) = isp->u.func;
+  token_chain *chain;
+
+  next_char (false);
+  assert (isp->type == INPUT_CHAIN);
+  chain = isp->u.u_c.chain;
+  assert (!chain->quote_age && chain->type == CHAIN_FUNC);
+  TOKEN_DATA_FUNC (td) = chain->u.func;
+  chain->u.func = NULL;
 }
 
 /*-------------------------------------------------------------------.
@@ -1537,7 +1664,6 @@ next_token (token_data *td, int *line, struct obstack 
*obs, bool allow_argv,
   if (ch == CHAR_MACRO)
     {
       init_macro_token (td);
-      next_char (false);
 #ifdef DEBUG_INPUT
       xfprintf (stderr, "next_token -> MACDEF (%s)\n",
                find_builtin_by_addr (TOKEN_DATA_FUNC (td))->name);
@@ -1564,20 +1690,31 @@ next_token (token_data *td, int *line, struct obstack 
*obs, bool allow_argv,
       if (obs)
        obs_td = obs;
       obstack_grow (obs_td, curr_comm.str1, curr_comm.len1);
-      while ((ch = next_char (false)) < CHAR_EOF
-            && !MATCH (ch, curr_comm.str2, true))
-       obstack_1grow (obs_td, ch);
-      if (ch != CHAR_EOF)
+      while (1)
        {
+         ch = next_char (false);
+         if (ch == CHAR_EOF)
+           /* current_file changed to "" if we see CHAR_EOF, use the
+              previous value we stored earlier.  */
+           m4_error_at_line (EXIT_FAILURE, 0, file, *line, caller,
+                             _("end of file in comment"));
+         if (ch == CHAR_MACRO)
+           {
+             token_data tmp;
+             // TODO support concatenation of builtins
+             m4_warn_at_line (0, file, *line, caller,
+                              _("cannot comment builtin"));
+             init_macro_token (&tmp);
+             continue;
+           }
+         if (MATCH (ch, curr_comm.str2, true))
+           {
+             obstack_grow (obs_td, curr_comm.str2, curr_comm.len2);
+             break;
+           }
          assert (ch < CHAR_EOF);
-         obstack_grow (obs_td, curr_comm.str2, curr_comm.len2);
+         obstack_1grow (obs_td, ch);
        }
-      else
-       /* current_file changed to "" if we see CHAR_EOF, use the
-          previous value we stored earlier.  */
-       m4_error_at_line (EXIT_FAILURE, 0, file, *line, caller,
-                         _("end of file in comment"));
-
       type = TOKEN_STRING;
     }
   else if (default_word_regexp && (isalpha (ch) || ch == '_'))
@@ -1633,6 +1770,7 @@ next_token (token_data *td, int *line, struct obstack 
*obs, bool allow_argv,
 
   else if (!MATCH (ch, curr_quote.str1, true))
     {
+      assert (ch < CHAR_EOF);
       switch (ch)
        {
        case '(':
@@ -1655,6 +1793,7 @@ next_token (token_data *td, int *line, struct obstack 
*obs, bool allow_argv,
       if (obs)
        obs_td = obs;
       quote_level = 1;
+      type = TOKEN_STRING;
       while (1)
        {
          ch = next_char (obs != NULL && current_quote_age);
@@ -1664,6 +1803,36 @@ next_token (token_data *td, int *line, struct obstack 
*obs, bool allow_argv,
            m4_error_at_line (EXIT_FAILURE, 0, file, *line, caller,
                              _("end of file in string"));
 
+         if (ch == CHAR_MACRO)
+           {
+             token_data tmp;
+             // TODO support concatenation of builtins
+             if (obstack_object_size (obs_td) == 0
+                 && TOKEN_DATA_TYPE (td) == TOKEN_VOID)
+               {
+                 assert (quote_level == 1);
+                 init_macro_token (td);
+                 ch = peek_input (false);
+                 if (MATCH (ch, curr_quote.str2, false))
+                   {
+#ifdef DEBUG_INPUT
+                     const builtin *bp
+                       = find_builtin_by_addr (TOKEN_DATA_FUNC (td));
+                     xfprintf (stderr, "next_token -> MACDEF (%s)\n",
+                               bp->name);
+#endif
+                     ch = next_char (false);
+                     MATCH (ch, curr_quote.str2, true);
+                     return TOKEN_MACDEF;
+                   }
+                 TOKEN_DATA_TYPE (td) = TOKEN_VOID;
+               }
+             else
+               init_macro_token (&tmp);
+             m4_warn_at_line (0, file, *line, caller,
+                              _("cannot quote builtin"));
+             continue;
+           }
          if (ch == CHAR_QUOTE)
            append_quote_token (obs, td);
          else if (MATCH (ch, curr_quote.str2, true))
@@ -1683,7 +1852,6 @@ next_token (token_data *td, int *line, struct obstack 
*obs, bool allow_argv,
              obstack_1grow (obs_td, ch);
            }
        }
-      type = TOKEN_STRING;
     }
 
   if (TOKEN_DATA_TYPE (td) == TOKEN_VOID)
@@ -1729,6 +1897,9 @@ next_token (token_data *td, int *line, struct obstack 
*obs, bool allow_argv,
                xfprintf (stderr, "%s", chain->u.u_s.str);
                len += chain->u.u_s.len;
                break;
+             case CHAIN_FUNC:
+               xfprintf (stderr, "<func>");
+               break;
              case CHAIN_ARGV:
                xfprintf (stderr, "address@hidden");
                break;
diff --git a/src/m4.h b/src/m4.h
index 712d32f..3f4e648 100644
--- a/src/m4.h
+++ b/src/m4.h
@@ -279,8 +279,9 @@ enum token_data_type
 enum token_chain_type
 {
   CHAIN_STR,   /* Link contains a string, u.u_s is valid.  */
-  // TODO add CHAIN_FUNC
-  CHAIN_ARGV   /* Link contains a $@ reference, u.u_a is valid.  */
+  CHAIN_FUNC,  /* Builtin function definition, u.func is valid.  */
+  CHAIN_ARGV,  /* Link contains a $@ reference, u.u_a is valid.  */
+  CHAIN_LOC    /* Link contains location of m4wrap, u.u_l is valid.  */
 };
 
 /* Composite tokens are built of a linked list of chains.  Each link
@@ -300,6 +301,7 @@ struct token_chain
          int level;            /* Expansion level of link content, or -1.  */
        }
       u_s;
+      builtin_func *func;              /* Builtin token from defn.  */
       struct
        {
          macro_arguments *argv;        /* Reference to earlier address@hidden  
*/
@@ -310,6 +312,12 @@ struct token_chain
          const string_pair *quotes;    /* NULL for $*, quotes for 
address@hidden  */
        }
       u_a;
+      struct
+       {
+         const char *file;     /* File where subsequent links originate.  */
+         int line;             /* Line where subsequent links originate.  */
+       }
+      u_l;
     }
   u;
 };
@@ -377,11 +385,11 @@ void skip_line (const char *);
 
 /* push back input */
 void push_file (FILE *, const char *, bool);
-void push_macro (builtin_func *);
+void push_macro (struct obstack *, builtin_func *);
 struct obstack *push_string_init (void);
 bool push_token (token_data *, int, bool);
 const input_block *push_string_finish (void);
-struct obstack *push_wrapup_init (void);
+struct obstack *push_wrapup_init (bool);
 void push_wrapup_finish (void);
 bool pop_wrapup (void);
 void input_print (struct obstack *, const input_block *);
diff --git a/src/macro.c b/src/macro.c
index bb7aad5..3625717 100644
--- a/src/macro.c
+++ b/src/macro.c
@@ -49,6 +49,8 @@ struct macro_arguments
   /* False if all arguments belong to this argv, true if some of them
      include references to another.  */
   bool_bitfield has_ref : 1;
+  /* True to flatten builtins contained in references.  */
+  bool_bitfield flatten : 1;
   const char *argv0; /* The macro name being expanded.  */
   size_t argv0_len; /* Length of argv0.  */
   /* The value of quote_age used when parsing all arguments in this
@@ -476,6 +478,7 @@ expand_argument (struct obstack *obs, token_data *argp, 
const char *caller)
          assert (paren_level == 0 && TOKEN_DATA_TYPE (argp) == TOKEN_VOID
                  && obstack_object_size (obs) == 0
                  && td.u.u_c.chain == td.u.u_c.end
+                 && td.u.u_c.chain->quote_age == age
                  && td.u.u_c.chain->type == CHAIN_ARGV);
          TOKEN_DATA_TYPE (argp) = TOKEN_COMP;
          argp->u.u_c.chain = argp->u.u_c.end = td.u.u_c.chain;
@@ -516,6 +519,7 @@ collect_arguments (symbol *sym, struct obstack *arguments,
   args.inuse = false;
   args.wrapper = false;
   args.has_ref = false;
+  args.flatten = !groks_macro_args;
   args.argv0 = SYMBOL_NAME (sym);
   args.argv0_len = strlen (args.argv0);
   args.quote_age = quote_age ();
@@ -811,19 +815,24 @@ arg_adjust_refcount (macro_arguments *argv, bool increase)
 /* Given ARGV, return the token_data that contains argument INDEX;
    INDEX must be > 0, < argv->argc.  If LEVEL is non-NULL, *LEVEL is
    set to the obstack level that contains the token (which is not
-   necessarily the level of ARGV).  */
+   necessarily the level of ARGV).  If FLATTEN, avoid returning a
+   builtin function.  */
 static token_data *
-arg_token (macro_arguments *argv, unsigned int index, int *level)
+arg_token (macro_arguments *argv, unsigned int index, int *level, bool flatten)
 {
   unsigned int i;
   token_data *token;
 
   assert (index && index < argv->argc);
+  flatten |= argv->flatten;
   if (!argv->wrapper)
     {
       if (level)
        *level = argv->level;
-      return argv->array[index - 1];
+      token = argv->array[index - 1];
+      if (flatten && TOKEN_DATA_TYPE (token) == TOKEN_FUNC)
+       token = &empty_token;
+      return token;
     }
   /* Must cycle through all tokens, until we find index, since a ref
      may occupy multiple indices.  */
@@ -838,10 +847,8 @@ arg_token (macro_arguments *argv, unsigned int index, int 
*level)
                       + (chain->u.u_a.skip_last ? 0 : 1)))
            {
              token = arg_token (chain->u.u_a.argv,
-                                chain->u.u_a.index - 1 + index, level);
-             if (chain->u.u_a.flatten
-                 && TOKEN_DATA_TYPE (token) == TOKEN_FUNC)
-               token = &empty_token;
+                                chain->u.u_a.index - 1 + index, level,
+                                flatten || chain->u.u_a.flatten);
              break;
            }
          index -= (chain->u.u_a.argv->argc - chain->u.u_a.index
@@ -874,7 +881,7 @@ arg_type (macro_arguments *argv, unsigned int index)
 
   if (index == 0 || index >= argv->argc)
     return TOKEN_TEXT;
-  token = arg_token (argv, index, NULL);
+  token = arg_token (argv, index, NULL, false);
   type = TOKEN_DATA_TYPE (token);
   /* When accessed via the arg_* interface, composite tokens are
      currently sequences of text only.  */
@@ -883,7 +890,7 @@ arg_type (macro_arguments *argv, unsigned int index)
   return type;
 }
 
-/* Given ARGV, return the text at argument INDEX, or NULL if the
+/* Given ARGV, return the text at argument INDEX; abort if the
    argument is not text.  Index 0 is always text, and indices beyond
    argc return the empty string.  */
 const char *
@@ -897,13 +904,11 @@ arg_text (macro_arguments *argv, unsigned int index)
     return argv->argv0;
   if (index >= argv->argc)
     return "";
-  token = arg_token (argv, index, NULL);
+  token = arg_token (argv, index, NULL, false);
   switch (TOKEN_DATA_TYPE (token))
     {
     case TOKEN_TEXT:
       return TOKEN_DATA_TEXT (token);
-    case TOKEN_FUNC:
-      return NULL;
     case TOKEN_COMP:
       // TODO - concatenate functions?
       chain = token->u.u_c.chain;
@@ -916,12 +921,16 @@ arg_text (macro_arguments *argv, unsigned int index)
            case CHAIN_STR:
              obstack_grow (obs, chain->u.u_s.str, chain->u.u_s.len);
              break;
+           case CHAIN_FUNC:
+             // TODO concatenate builtins
+             assert (!"implemented");
+             abort ();
            case CHAIN_ARGV:
              arg_print (obs, chain->u.u_a.argv, chain->u.u_a.index,
                         quote_cache (NULL, chain->quote_age,
                                      chain->u.u_a.quotes),
-                        chain->u.u_a.flatten, chain->u.u_a.skip_last, NULL,
-                        NULL);
+                        argv->flatten || chain->u.u_a.flatten,
+                        chain->u.u_a.skip_last, NULL, NULL);
              break;
            default:
              assert (!"arg_text");
@@ -931,6 +940,7 @@ arg_text (macro_arguments *argv, unsigned int index)
        }
       obstack_1grow (obs, '\0');
       return (char *) obstack_finish (obs);
+    case TOKEN_FUNC:
     default:
       break;
     }
@@ -945,8 +955,8 @@ arg_text (macro_arguments *argv, unsigned int index)
 bool
 arg_equal (macro_arguments *argv, unsigned int indexa, unsigned int indexb)
 {
-  token_data *ta = arg_token (argv, indexa, NULL);
-  token_data *tb = arg_token (argv, indexb, NULL);
+  token_data *ta = arg_token (argv, indexa, NULL, false);
+  token_data *tb = arg_token (argv, indexb, NULL, false);
   token_chain tmpa;
   token_chain tmpb;
   token_chain *ca = &tmpa;
@@ -963,30 +973,45 @@ arg_equal (macro_arguments *argv, unsigned int indexa, 
unsigned int indexb)
                       TOKEN_DATA_LEN (ta)) == 0);
 
   /* Convert both arguments to chains, if not one already.  */
-  // TODO - allow builtin tokens in the comparison?
-  if (TOKEN_DATA_TYPE (ta) == TOKEN_TEXT)
+  switch (TOKEN_DATA_TYPE (ta))
     {
+    case TOKEN_TEXT:
       tmpa.next = NULL;
       tmpa.type = CHAIN_STR;
       tmpa.u.u_s.str = TOKEN_DATA_TEXT (ta);
       tmpa.u.u_s.len = TOKEN_DATA_LEN (ta);
-    }
-  else
-    {
-      assert (TOKEN_DATA_TYPE (ta) == TOKEN_COMP);
+      break;
+    case TOKEN_FUNC:
+      tmpa.next = NULL;
+      tmpa.type = CHAIN_FUNC;
+      tmpa.u.func = TOKEN_DATA_FUNC (ta);
+      break;
+    case TOKEN_COMP:
       ca = ta->u.u_c.chain;
+      break;
+    default:
+      assert (!"arg_equal");
+      abort ();
     }
-  if (TOKEN_DATA_TYPE (tb) == TOKEN_TEXT)
+  switch (TOKEN_DATA_TYPE (tb))
     {
+    case TOKEN_TEXT:
       tmpb.next = NULL;
       tmpb.type = CHAIN_STR;
       tmpb.u.u_s.str = TOKEN_DATA_TEXT (tb);
       tmpb.u.u_s.len = TOKEN_DATA_LEN (tb);
-    }
-  else
-    {
-      assert (TOKEN_DATA_TYPE (tb) == TOKEN_COMP);
+      break;
+    case TOKEN_FUNC:
+      tmpb.next = NULL;
+      tmpb.type = CHAIN_FUNC;
+      tmpb.u.func = TOKEN_DATA_FUNC (tb);
+      break;
+    case TOKEN_COMP:
       cb = tb->u.u_c.chain;
+      break;
+    default:
+      assert (!"arg_equal");
+      abort ();
     }
 
   /* Compare each link of the chain.  */
@@ -1016,7 +1041,14 @@ arg_equal (macro_arguments *argv, unsigned int indexa, 
unsigned int indexb)
          cb = &tmpb;
          continue;
        }
-      // TODO support comparison against $@ refs.
+      if (ca->type == CHAIN_FUNC)
+       {
+         if (cb->type != CHAIN_FUNC || ca->u.func != cb->u.func)
+           return false;
+         ca = ca->next;
+         cb = cb->next;
+         continue;
+       }
       assert (ca->type == CHAIN_STR && cb->type == CHAIN_STR);
       if (ca->u.u_s.len == cb->u.u_s.len)
        {
@@ -1066,10 +1098,10 @@ arg_empty (macro_arguments *argv, unsigned int index)
     return argv->argv0_len == 0;
   if (index >= argv->argc)
     return true;
-  return arg_token (argv, index, NULL) == &empty_token;
+  return arg_token (argv, index, NULL, false) == &empty_token;
 }
 
-/* Given ARGV, return the length of argument INDEX, or SIZE_MAX if the
+/* Given ARGV, return the length of argument INDEX.  Abort if the
    argument is not text.  Indices beyond argc return 0.  */
 size_t
 arg_len (macro_arguments *argv, unsigned int index)
@@ -1082,14 +1114,12 @@ arg_len (macro_arguments *argv, unsigned int index)
     return argv->argv0_len;
   if (index >= argv->argc)
     return 0;
-  token = arg_token (argv, index, NULL);
+  token = arg_token (argv, index, NULL, false);
   switch (TOKEN_DATA_TYPE (token))
     {
     case TOKEN_TEXT:
       assert ((token == &empty_token) == (TOKEN_DATA_LEN (token) == 0));
       return TOKEN_DATA_LEN (token);
-    case TOKEN_FUNC:
-      return SIZE_MAX;
     case TOKEN_COMP:
       // TODO - concatenate functions?
       chain = token->u.u_c.chain;
@@ -1104,6 +1134,10 @@ arg_len (macro_arguments *argv, unsigned int index)
            case CHAIN_STR:
              len += chain->u.u_s.len;
              break;
+           case CHAIN_FUNC:
+             // TODO concatenate builtins
+             assert (!"implemented");
+             abort ();
            case CHAIN_ARGV:
              i = chain->u.u_a.index;
              limit = (chain->u.u_a.argv->argc - i
@@ -1114,7 +1148,14 @@ arg_len (macro_arguments *argv, unsigned int index)
              len += ((quotes ? (quotes->len1 + quotes->len2 + 1) : 1) * limit
                      - 1);
              while (limit--)
-               len += arg_len (chain->u.u_a.argv, i++);
+               {
+                 if (TOKEN_DATA_TYPE (arg_token (chain->u.u_a.argv, i, NULL,
+                                                 false)) == TOKEN_FUNC)
+                   assert (argv->flatten);
+                 else
+                   len += arg_len (chain->u.u_a.argv, i);
+                 i++;
+               }
              break;
            default:
              assert (!"arg_len");
@@ -1124,6 +1165,7 @@ arg_len (macro_arguments *argv, unsigned int index)
        }
       assert (len);
       return len;
+    case TOKEN_FUNC:
     default:
       break;
     }
@@ -1141,7 +1183,7 @@ arg_func (macro_arguments *argv, unsigned int index)
 
   if (index == 0 || index >= argv->argc)
     return NULL;
-  token = arg_token (argv, index, NULL);
+  token = arg_token (argv, index, NULL, false);
   switch (TOKEN_DATA_TYPE (token))
     {
     case TOKEN_FUNC:
@@ -1197,7 +1239,7 @@ arg_print (struct obstack *obs, macro_arguments *argv, 
unsigned int index,
        return true;
       else
        use_sep = true;
-      token = arg_token (argv, i, NULL);
+      token = arg_token (argv, i, NULL, flatten);
       switch (TOKEN_DATA_TYPE (token))
        {
        case TOKEN_TEXT:
@@ -1222,6 +1264,10 @@ arg_print (struct obstack *obs, macro_arguments *argv, 
unsigned int index,
                                     &len))
                    return true;
                  break;
+               case CHAIN_FUNC:
+                 func_print (obs, find_builtin_by_addr (chain->u.func),
+                             flatten, quotes);
+                 break;
                case CHAIN_ARGV:
                  if (arg_print (obs, chain->u.u_a.argv, chain->u.u_a.index,
                                 quote_cache (NULL, chain->quote_age,
@@ -1387,6 +1433,7 @@ make_argv_ref (macro_arguments *argv, const char *argv0, 
size_t argv0_len,
       new_argv->arraylen = 0;
       new_argv->wrapper = false;
       new_argv->has_ref = false;
+      new_argv->flatten = false;
     }
   else
     {
@@ -1396,6 +1443,7 @@ make_argv_ref (macro_arguments *argv, const char *argv0, 
size_t argv0_len,
       new_argv->array[0] = token;
       new_argv->wrapper = true;
       new_argv->has_ref = argv->has_ref;
+      new_argv->flatten = flatten;
     }
   new_argv->argc = argv->argc - (index - 1);
   new_argv->inuse = false;
@@ -1432,9 +1480,9 @@ push_arg_quote (struct obstack *obs, macro_arguments 
*argv, unsigned int index,
                const string_pair *quotes)
 {
   int level;
-  token_data *token = arg_token (argv, index, &level);
+  token_data *token = arg_token (argv, index, &level, false);
 
-  // TODO handle func tokens?
+  // TODO handle func tokens inside quotes?
   if (quotes)
     obstack_grow (obs, quotes->str1, quotes->len1);
   argv->inuse |= push_token (token, level, argv->inuse);
@@ -1464,8 +1512,7 @@ push_args (struct obstack *obs, macro_arguments *argv, 
bool skip, bool quote)
       return;
     }
 
-  // TODO allow shift, $@, to push builtins without flatten?
-  token = make_argv_ref_token (&td, obs, -1, argv, i, true,
+  token = make_argv_ref_token (&td, obs, -1, argv, i, argv->flatten,
                               quote ? &curr_quote : NULL);
   assert (token);
   argv->inuse |= push_token (token, -1, argv->inuse);


hooks/post-receive
--
GNU M4 source repository




reply via email to

[Prev in Thread] Current Thread [Next in Thread]