m4-patches
[Top][All Lists]
Advanced

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

argv_ref patch 21: push builtin tokens through $@ and m4wrap


From: Eric Blake
Subject: argv_ref patch 21: push builtin tokens through $@ and m4wrap
Date: Mon, 14 Apr 2008 22:28:55 -0600
User-agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.12) Gecko/20080213 Thunderbird/2.0.0.12 Mnenhy/0.7.5.666

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Next round of patches (I've been delayed a bit on the argv_ref porting
efforts by releasing m4 1.4.11 and autoconf 2.62).  On the master branch,
I split this into two patches.  The first provides about 1.5% speedup in
execution, by adding a sentinel at the end of the input stack to reduce
the number of branches encountered when deciding, for each input token,
whether end of input had been reached yet.  The second has minimal
impacts.  It exploits the fact that stage 19 added support for builtin
tokens as part of a reference chain, and removes lone builtin tokens from
the input engine in favor of reference chains.  By adding a parameter to
arg_print, it is now possible to paste a builtin token onto a chain being
constructed on an obstack, rather than being forced to flatten it to empty
quotes or a string representation of the builtin's name.  With this in
place, m4wrap and $@ references can now transparently concatenate builtin
tokens with other text.  However, there are still other places that are
inconsistent on the behavior of builtin tokens encountered adjacent to
other text, saved for another patch.

One nice bit about this patch is we now match Solaris m4 behavior on this
(admittedly contrived) testcase:
m4wrap(`define(foo,')m4wrap(defn(`divnum'))m4wrap(`)foo
')
=> 0

2008-04-14  Eric Blake  <address@hidden>

        Stage 21: $@ concatenates builtins, m4wrap takes builtins.
        Create a new input block type, which always fails with CHAR_EOF,
        so that remaining input routines no longer have to check for NULL
        input block.  Improve arg_print to handle builtin tokens when
        printing to a known chain, rather than always flattening builtins,
        allowing m4wrap and $@ references to handle embedded builtins.
        Memory impact: none.
        Speed impact: noticeable improvement, from fewer conditionals.
        * src/m4.h (append_macro): New prototype.
        (push_macro, push_wrapup_init, arg_print, func_print): Alter
        prototypes.
        * src/input.c (INPUT_MACRO): Delete, covered by INPUT_CHAIN.
        (INPUT_EOF): New input block type, for efficiency.
        (struct input_block): Remove u.func member.
        (input_eof): New input sentinel.
        (append_macro): New function.
        (push_macro, push_wrapup_init): Add parameter.
        (push_token): Support builtin tokens.
        (init_macro_token): Always use chain for builtin tokens.
        (push_string_init, pop_input, pop_wrapup, input_print)
        (peek_input, next_char, next_char_1, input_init): Adjust callers.
        * src/macro.c (arg_equal, wrap_args): Handle builtin tokens.
        (arg_print): Add parameter.
        (collect_arguments, arg_type, arg_text): Adjust callers.
        * src/builtin.c (m4_m4wrap): Handle builtin tokens.
        (func_print): Add parameter.
        (m4_defn): Allow pushing builtin alongside other text.
        (m4_errprint): Adjust caller.
        * src/debug.c (trace_pre): Likewise.
        * doc/m4.texinfo (Defn, Ifelse, Debug Levels): Update tests to new
        behavior.
        (M4wrap): New test.

- --
Don't work too hard, make some time for fun as well!

Eric Blake             address@hidden
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Public key at home.comcast.net/~ericblake/eblake.gpg
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkgELwcACgkQ84KuGfSFAYCe7wCfU5GK1Lphr7KhXmyh/JU0+3al
casAoMJgS7nnalhYJHKe8XwbKd7k7Ohy
=fCMm
-----END PGP SIGNATURE-----
>From b6964b5f0e6b81833bfbf1aee8bb49c35b09c3f3 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Mon, 14 Apr 2008 08:16:32 -0600
Subject: [PATCH] Stage 21a: Optimize checks for end of input.

* m4/input.c (eof_funcs, input_eof): New objects.
(eof_peek, eof_read, eof_unget): New functions.
(file_clean, m4_push_string_init, pop_input, m4_push_wrapup_init)
(m4_pop_wrapup, next_char, peek_char, unget_input, m4_input_init)
(m4_input_exit): Use placeholder to guarantee non-NULL isp and
wsp.
(next_char): Rename retry to allow_unget, and change sense for
easier manipulation.  All callers changed.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog  |   15 ++++++
 m4/input.c |  144 ++++++++++++++++++++++++++++++++++++------------------------
 2 files changed, 101 insertions(+), 58 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index af55ae5..3ec4e10 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,20 @@
 2008-04-14  Eric Blake  <address@hidden>
 
+       Stage 21a: Optimize checks for end of input.
+       Create a new polymorphic input block type, which always fails with
+       CHAR_EOF, so that remaining input routines no longer have to check
+       for NULL input block.
+       Memory impact: none.
+       Speed impact: noticeable improvement, from fewer conditionals.
+       * m4/input.c (eof_funcs, input_eof): New objects.
+       (eof_peek, eof_read, eof_unget): New functions.
+       (file_clean, m4_push_string_init, pop_input, m4_push_wrapup_init)
+       (m4_pop_wrapup, next_char, peek_char, unget_input, m4_input_init)
+       (m4_input_exit): Use placeholder to guarantee non-NULL isp and
+       wsp.
+       (next_char): Rename retry to allow_unget, and change sense for
+       easier manipulation.  All callers changed.
+
        Improve OS/2 detection.
        * modules/gnu.c (m4_macro_table): Ensure all possible identifiers
        are defined, not just the first.  The testsuite ensures that
diff --git a/m4/input.c b/m4/input.c
index a7f1da9..3607fa7 100644
--- a/m4/input.c
+++ b/m4/input.c
@@ -114,6 +114,10 @@ static     int     composite_read          (m4_input_block 
*, m4 *, bool, bool,
 static void    composite_unget         (m4_input_block *, int);
 static bool    composite_clean         (m4_input_block *, m4 *, bool);
 static void    composite_print         (m4_input_block *, m4 *, m4_obstack *);
+static int     eof_peek                (m4_input_block *, m4 *, bool);
+static int     eof_read                (m4_input_block *, m4 *, bool, bool,
+                                        bool);
+static void    eof_unget               (m4_input_block *, int);
 
 static void    init_builtin_token      (m4 *, m4_symbol_value *);
 static void    append_quote_token      (m4 *, m4_obstack *,
@@ -143,10 +147,10 @@ struct input_funcs
   /* Read input, return an unsigned char, CHAR_BUILTIN if it is a
      builtin, or CHAR_RETRY if none available.  If ALLOW_QUOTE, then
      CHAR_QUOTE may be returned.  If ALLOW_ARGV, then CHAR_ARGV may be
-     returned.  If SAFE, then do not alter the current file or
-     line.  */
+     returned.  If ALLOW_UNGET, then ensure that the next unget_func
+     will work with the returned character.  */
   int  (*read_func)    (m4_input_block *, m4 *, bool allow_quote,
-                        bool allow_argv, bool safe);
+                        bool allow_argv, bool allow_unget);
 
   /* Unread a single unsigned character or CHAR_BUILTIN, must be the
      same character previously read by read_func.  */
@@ -213,13 +217,14 @@ static m4_obstack *current_input;
 /* Bottom of token_stack, for obstack_free.  */
 static void *token_bottom;
 
-/* Pointer to top of current_input.  */
+/* Pointer to top of current_input, never NULL.  */
 static m4_input_block *isp;
 
-/* Pointer to top of wrapup_stack.  */
+/* Pointer to top of wrapup_stack, never NULL.  */
 static m4_input_block *wsp;
 
-/* Aux. for handling split m4_push_string ().  */
+/* Auxiliary for handling split m4_push_string (), NULL when not
+   pushing text for rescanning.  */
 static m4_input_block *next;
 
 /* Flag for next_char () to increment current_line.  */
@@ -249,6 +254,14 @@ static struct input_funcs composite_funcs = {
   composite_print
 };
 
+/* Vtable for recognizing end of input.  */
+static struct input_funcs eof_funcs = {
+  eof_peek, eof_read, eof_unget, NULL, NULL
+};
+
+/* Marker at end of an input stack.  */
+static m4_input_block input_eof = { NULL, &eof_funcs, "", 0 };
+
 
 /* Input files, from command line or [s]include.  */
 static int
@@ -270,7 +283,7 @@ file_peek (m4_input_block *me, m4 *context M4_GNUC_UNUSED,
 
 static int
 file_read (m4_input_block *me, m4 *context, bool allow_quote M4_GNUC_UNUSED,
-          bool allow_argv M4_GNUC_UNUSED, bool safe M4_GNUC_UNUSED)
+          bool allow_argv M4_GNUC_UNUSED, bool allow_unget M4_GNUC_UNUSED)
 {
   int ch;
 
@@ -313,7 +326,7 @@ file_clean (m4_input_block *me, m4 *context, bool cleanup)
 {
   if (!cleanup)
     return false;
-  if (me->prev)
+  if (me->prev != &input_eof)
     m4_debug_message (context, M4_DEBUG_TRACE_INPUT,
                      _("input reverted to %s, line %d"),
                      me->prev->file, me->prev->line);
@@ -397,7 +410,7 @@ builtin_peek (m4_input_block *me, m4 *context 
M4_GNUC_UNUSED,
 static int
 builtin_read (m4_input_block *me, m4 *context M4_GNUC_UNUSED,
              bool allow_quote M4_GNUC_UNUSED, bool allow_argv M4_GNUC_UNUSED,
-             bool safe M4_GNUC_UNUSED)
+             bool allow_unget M4_GNUC_UNUSED)
 {
   /* Not consumed here - wait until init_builtin_token.  */
   return me->u.builtin ? CHAR_BUILTIN : CHAR_RETRY;
@@ -457,7 +470,7 @@ string_peek (m4_input_block *me, m4 *context M4_GNUC_UNUSED,
 static int
 string_read (m4_input_block *me, m4 *context M4_GNUC_UNUSED,
             bool allow_quote M4_GNUC_UNUSED, bool allow_argv M4_GNUC_UNUSED,
-            bool safe M4_GNUC_UNUSED)
+            bool allow_unget M4_GNUC_UNUSED)
 {
   if (!me->u.u_s.len)
     return CHAR_RETRY;
@@ -492,7 +505,7 @@ m4_push_string_init (m4 *context)
 {
   /* Free any memory occupied by completely parsed input.  */
   assert (!next);
-  while (isp && pop_input (context, false));
+  while (pop_input (context, false));
 
   /* Reserve the next location on the obstack.  */
   next = (m4_input_block *) obstack_alloc (current_input, sizeof *next);
@@ -764,7 +777,7 @@ composite_peek (m4_input_block *me, m4 *context, bool 
allow_argv)
 
 static int
 composite_read (m4_input_block *me, m4 *context, bool allow_quote,
-               bool allow_argv, bool safe)
+               bool allow_argv, bool allow_unget)
 {
   m4__symbol_chain *chain = me->u.u_c.chain;
   size_t argc;
@@ -818,13 +831,13 @@ composite_read (m4_input_block *me, m4 *context, bool 
allow_quote,
          chain->u.u_a.index++;
          chain->u.u_a.comma = true;
          m4_push_string_finish ();
-         return next_char (context, allow_quote, allow_argv, !safe);
+         return next_char (context, allow_quote, allow_argv, allow_unget);
        case M4__CHAIN_LOC:
          me->file = chain->u.u_l.file;
          me->line = chain->u.u_l.line;
          input_change = true;
          me->u.u_c.chain = chain->next;
-         return next_char (context, allow_quote, allow_argv, !safe);
+         return next_char (context, allow_quote, allow_argv, allow_unget);
        default:
          assert (!"composite_read");
          abort ();
@@ -976,6 +989,33 @@ m4__make_text_link (m4_obstack *obs, m4__symbol_chain 
**start,
 
 
 
+/* End of input optimization.  By providing these dummy callback
+   functions, we guarantee that the input stack is never NULL, and
+   thus make fewer execution branches.  */
+static int
+eof_peek (m4_input_block *me, m4 *context M4_GNUC_UNUSED,
+          bool allow_argv M4_GNUC_UNUSED)
+{
+  assert (me == &input_eof);
+  return CHAR_EOF;
+}
+
+static int
+eof_read (m4_input_block *me, m4 *context M4_GNUC_UNUSED,
+         bool allow_quote M4_GNUC_UNUSED, bool allow_argv M4_GNUC_UNUSED,
+         bool allow_unget M4_GNUC_UNUSED)
+{
+  assert (me == &input_eof);
+  return CHAR_EOF;
+}
+
+static void
+eof_unget (m4_input_block *me M4_GNUC_UNUSED, int ch)
+{
+  assert (ch == CHAR_EOF);
+}
+
+
 /* When tracing, print a summary of the contents of the input block
    created by push_string_init/push_string_finish to OBS.  */
 void
@@ -1010,7 +1050,7 @@ m4_push_wrapup_init (m4 *context)
   m4__symbol_chain *chain;
 
   assert (obstack_object_size (wrapup_stack) == 0);
-  if (wsp)
+  if (wsp != &input_eof)
     {
       i = wsp;
       assert (i->funcs == &composite_funcs && i->u.u_c.end
@@ -1064,12 +1104,9 @@ pop_input (m4 *context, bool cleanup)
       : (isp->funcs->peek_func (isp, context, true) != CHAR_RETRY))
     return false;
 
-  if (tmp != NULL)
-    {
-      obstack_free (current_input, isp);
-      m4__quote_uncache (M4SYNTAX);
-      next = NULL;     /* might be set in m4_push_string_init () */
-    }
+  obstack_free (current_input, isp);
+  m4__quote_uncache (M4SYNTAX);
+  next = NULL; /* might be set in m4_push_string_init () */
 
   isp = tmp;
   input_change = true;
@@ -1088,7 +1125,7 @@ m4_pop_wrapup (m4 *context)
   obstack_free (current_input, NULL);
   free (current_input);
 
-  if (wsp == NULL)
+  if (wsp == &input_eof)
     {
       obstack_free (wrapup_stack, NULL);
       m4_set_current_file (context, NULL);
@@ -1108,7 +1145,7 @@ m4_pop_wrapup (m4 *context)
   obstack_init (wrapup_stack);
 
   isp = wsp;
-  wsp = NULL;
+  wsp = &input_eof;
   input_change = true;
 
   return true;
@@ -1251,21 +1288,15 @@ init_argv_symbol (m4 *context, m4_obstack *obs, 
m4_symbol_value *value)
    for append_quote_token; otherwise, if ALLOW_ARGV, and the current
    input matches an argv reference with the correct quoting, return
    CHAR_ARGV and leave consumption of data for init_argv_symbol.  If
-   RETRY, then avoid returning CHAR_RETRY by popping input.  */
+   ALLOW_UNGET, then pop input to avoid returning CHAR_RETRY, and
+   ensure that unget_input can safely be called next.  */
 static int
-next_char (m4 *context, bool allow_quote, bool allow_argv, bool retry)
+next_char (m4 *context, bool allow_quote, bool allow_argv, bool allow_unget)
 {
   int ch;
 
   while (1)
     {
-      if (isp == NULL)
-       {
-         m4_set_current_file (context, NULL);
-         m4_set_current_line (context, 0);
-         return CHAR_EOF;
-       }
-
       if (input_change)
        {
          m4_set_current_file (context, isp->file);
@@ -1274,9 +1305,9 @@ next_char (m4 *context, bool allow_quote, bool 
allow_argv, bool retry)
 
       assert (isp->funcs->read_func);
       while (((ch = isp->funcs->read_func (isp, context, allow_quote,
-                                          allow_argv, !retry))
+                                          allow_argv, allow_unget))
              != CHAR_RETRY)
-            || !retry)
+            || allow_unget)
        {
          /* if (!IS_IGNORE (ch)) */
          return ch;
@@ -1299,15 +1330,12 @@ peek_char (m4 *context, bool allow_argv)
 
   while (1)
     {
-      if (block == NULL)
-       return CHAR_EOF;
-
       assert (block->funcs->peek_func);
       ch = block->funcs->peek_func (block, context, allow_argv);
       if (ch != CHAR_RETRY)
        {
 /*       if (IS_IGNORE (ch)) */
-/*         return next_char (context, false, true, true); */
+/*         return next_char (context, false, true, false); */
          return ch;
        }
 
@@ -1317,11 +1345,11 @@ peek_char (m4 *context, bool allow_argv)
 
 /* The function unget_input () puts back a character on the input
    stack, using an existing input_block if possible.  This is not safe
-   to call except immediately after next_char(context, aq, aa, false).  */
+   to call except immediately after next_char(context, aq, aa, true).  */
 static void
 unget_input (int ch)
 {
-  assert (isp != NULL && isp->funcs->unget_func != NULL);
+  assert (isp->funcs->unget_func != NULL);
   isp->funcs->unget_func (isp, ch);
 }
 
@@ -1335,7 +1363,7 @@ m4_skip_line (m4 *context, const char *name)
   const char *file = m4_get_current_file (context);
   int line = m4_get_current_line (context);
 
-  while ((ch = next_char (context, false, false, true)) != CHAR_EOF
+  while ((ch = next_char (context, false, false, false)) != CHAR_EOF
         && ch != '\n')
     ;
   if (ch == CHAR_EOF)
@@ -1381,14 +1409,14 @@ match_input (m4 *context, const char *s, bool consume)
   if (s[1] == '\0')
     {
       if (consume)
-       next_char (context, false, false, true);
+       next_char (context, false, false, false);
       return true;                     /* short match */
     }
 
-  next_char (context, false, false, true);
+  next_char (context, false, false, false);
   for (n = 1, t = s++; (ch = peek_char (context, false)) == to_uchar (*s++); )
     {
-      next_char (context, false, false, true);
+      next_char (context, false, false, false);
       n++;
       if (*s == '\0')          /* long match */
        {
@@ -1435,7 +1463,7 @@ consume_syntax (m4 *context, m4_obstack *obs, unsigned 
int syntax)
         by CHAR_RETRY.  We exploit the fact that CHAR_EOF,
         CHAR_BUILTIN, CHAR_QUOTE, and CHAR_ARGV do not satisfy any
         syntax categories.  */
-      while ((ch = next_char (context, allow, allow, false)) != CHAR_RETRY
+      while ((ch = next_char (context, allow, allow, true)) != CHAR_RETRY
             && m4_has_syntax (M4SYNTAX, ch, syntax))
        {
          assert (ch < CHAR_EOF);
@@ -1448,7 +1476,7 @@ consume_syntax (m4 *context, m4_obstack *obs, unsigned 
int syntax)
            {
              assert (ch < CHAR_EOF);
              obstack_1grow (obs, ch);
-             next_char (context, false, false, true);
+             next_char (context, false, false, false);
              continue;
            }
          return ch == CHAR_EOF;
@@ -1478,8 +1506,8 @@ m4_input_init (m4 *context)
   obstack_init (&token_stack);
   token_bottom = obstack_finish (&token_stack);
 
-  isp = NULL;
-  wsp = NULL;
+  isp = &input_eof;
+  wsp = &input_eof;
   next = NULL;
 
   start_of_input_line = false;
@@ -1489,8 +1517,8 @@ m4_input_init (m4 *context)
 void
 m4_input_exit (void)
 {
-  assert (current_input == NULL);
-  assert (wrapup_stack == NULL);
+  assert (!current_input && isp == &input_eof);
+  assert (!wrapup_stack && wsp == &input_eof);
   obstack_free (&file_names, NULL);
   obstack_free (&token_stack, NULL);
 }
@@ -1536,7 +1564,7 @@ m4__next_token (m4 *context, m4_symbol_value *token, int 
*line,
 
     /* Must consume an input character.  */
     ch = next_char (context, false, allow_argv && m4__quote_age (M4SYNTAX),
-                   true);
+                   false);
     if (ch == CHAR_EOF)                        /* EOF */
       {
 #ifdef DEBUG_INPUT
@@ -1568,7 +1596,7 @@ m4__next_token (m4 *context, m4_symbol_value *token, int 
*line,
     if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_ESCAPE))
       {                                        /* ESCAPED WORD */
        obstack_1grow (&token_stack, ch);
-       if ((ch = next_char (context, false, false, true)) < CHAR_EOF)
+       if ((ch = next_char (context, false, false, false)) < CHAR_EOF)
          {
            obstack_1grow (&token_stack, ch);
            if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_ALPHA))
@@ -1597,7 +1625,7 @@ m4__next_token (m4 *context, m4_symbol_value *token, int 
*line,
        while (1)
          {
            ch = next_char (context, obs && m4__quote_age (M4SYNTAX), false,
-                           true);
+                           false);
            if (ch == CHAR_EOF)
              m4_error_at_line (context, EXIT_FAILURE, 0, file, *line, caller,
                                _("end of file in string"));
@@ -1614,7 +1642,7 @@ m4__next_token (m4 *context, m4_symbol_value *token, int 
*line,
                    ch = peek_char (context, false);
                    if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_RQUOTE))
                      {
-                       ch = next_char (context, false, false, true);
+                       ch = next_char (context, false, false, false);
 #ifdef DEBUG_INPUT
                        m4_print_token (context, "next_token", M4_TOKEN_MACDEF,
                                        token);
@@ -1656,7 +1684,7 @@ m4__next_token (m4 *context, m4_symbol_value *token, int 
*line,
        assert (!m4__quote_age (M4SYNTAX));
        while (1)
          {
-           ch = next_char (context, false, false, true);
+           ch = next_char (context, false, false, false);
            if (ch == CHAR_EOF)
              m4_error_at_line (context, EXIT_FAILURE, 0, file, *line, caller,
                                _("end of file in string"));
@@ -1674,7 +1702,7 @@ m4__next_token (m4 *context, m4_symbol_value *token, int 
*line,
                    if (MATCH (context, ch, context->syntax->quote.str2,
                               false))
                      {
-                       ch = next_char (context, false, false, true);
+                       ch = next_char (context, false, false, false);
                        MATCH (context, ch, context->syntax->quote.str2, true);
 #ifdef DEBUG_INPUT
                        m4_print_token (context, "next_token", M4_TOKEN_MACDEF,
@@ -1714,7 +1742,7 @@ m4__next_token (m4 *context, m4_symbol_value *token, int 
*line,
        obstack_1grow (obs_safe, ch);
        while (1)
          {
-           ch = next_char (context, false, false, true);
+           ch = next_char (context, false, false, false);
            if (ch == CHAR_EOF)
              m4_error_at_line (context, EXIT_FAILURE, 0, file, *line, caller,
                                _("end of file in comment"));
@@ -1746,7 +1774,7 @@ m4__next_token (m4 *context, m4_symbol_value *token, int 
*line,
                      context->syntax->comm.len1);
        while (1)
          {
-           ch = next_char (context, false, false, true);
+           ch = next_char (context, false, false, false);
            if (ch == CHAR_EOF)
              m4_error_at_line (context, EXIT_FAILURE, 0, file, *line, caller,
                                _("end of file in comment"));
-- 
1.5.5


>From 0fe33c9c7dbdbbf98255a8779bf179854753df61 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Mon, 14 Apr 2008 17:02:36 -0600
Subject: [PATCH] Stage 21b: $@ concatenates builtins, m4wrap takes builtins.

* m4/m4module.h (m4_push_builtin): Add parameter.
(m4_builtin_print, m4_push_wrapup_init, m4_push_wrapup_finish)
(m4_arg_print, m4_symbol_value_print): Rename and reduce scope...
* m4/m4private.h (m4__builtin_print, m4__push_wrapup_init)
(m4__push_wrapup_finish, m4__arg_print, m4__symbol_value_print):
...to these, in some cases adding a parameter.
(m4__append_builtin): New prototype.
* m4/builtin.c (m4_builtin_print): Alter signature to print
builtin to a growing symbol chain.
* m4/symtab.c (m4__symbol_value_print): Alter signature.
(m4_symbol_print, dump_symbol_CB): Adjust callers.
* m4/input.c (builtin_peek, builtin_read, builtin_unget)
(builtin_print, builtin_funcs): Delete, handled via composite
blocks now.
(struct m4_input_block): Delete u.builtin member.
(init_builtin_token): Only use composite block.
(m4__append_builtin): New function.
(m4_push_builtin, m4__push_wrapup_init): Alter signature.
(m4__push_symbol): Allow builtin tokens.
(m4__push_wrapup_finish): Rename.
(composite_print, m4_print_token): Adjust callers.
* m4/macro.c (m4_wrap_args, collect_arguments): Allow builtin
tokens.
(m4__arg_print): Alter signature.
(trace_prepre, trace_pre, m4_arg_text, m4_arg_equal): Adjust
callers.
* modules/m4.c (m4wrap): Allow builtin tokens.
(defn, errprint): Adjust callers.
* modules/gnu.c (builtin): Likewise.
* doc/m4.texinfo (M4wrap): New test.
(Debuglen): Adjust expected output.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog      |   38 ++++++++++
 doc/m4.texinfo |   22 ++++++-
 m4/builtin.c   |   16 +++--
 m4/input.c     |  207 +++++++++++++++++++++++---------------------------------
 m4/m4module.h  |   12 +---
 m4/m4private.h |   19 +++++-
 m4/macro.c     |  130 ++++++++++++++++++-----------------
 m4/symtab.c    |   56 ++++++++-------
 modules/gnu.c  |    2 +-
 modules/m4.c   |   18 +++--
 10 files changed, 276 insertions(+), 244 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 3ec4e10..0869fc5 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,43 @@
 2008-04-14  Eric Blake  <address@hidden>
 
+       Stage 21b: $@ concatenates builtins, m4wrap takes builtins.
+       Improve arg_print to handle builtin tokens when printing to a
+       known chain, rather than always flattening builtins.  This allows
+       m4wrap and $@ back-references to handle embedded builtin tokens.
+       Memory impact: none.
+       Speed impact: slight penalty, from more bookkeeping.
+       * m4/m4module.h (m4_push_builtin): Add parameter.
+       (m4_builtin_print, m4_push_wrapup_init, m4_push_wrapup_finish)
+       (m4_arg_print, m4_symbol_value_print): Rename and reduce scope...
+       * m4/m4private.h (m4__builtin_print, m4__push_wrapup_init)
+       (m4__push_wrapup_finish, m4__arg_print, m4__symbol_value_print):
+       ...to these, in some cases adding a parameter.
+       (m4__append_builtin): New prototype.
+       * m4/builtin.c (m4_builtin_print): Alter signature to print
+       builtin to a growing symbol chain.
+       * m4/symtab.c (m4__symbol_value_print): Alter signature.
+       (m4_symbol_print, dump_symbol_CB): Adjust callers.
+       * m4/input.c (builtin_peek, builtin_read, builtin_unget)
+       (builtin_print, builtin_funcs): Delete, handled via composite
+       blocks now.
+       (struct m4_input_block): Delete u.builtin member.
+       (init_builtin_token): Only use composite block.
+       (m4__append_builtin): New function.
+       (m4_push_builtin, m4__push_wrapup_init): Alter signature.
+       (m4__push_symbol): Allow builtin tokens.
+       (m4__push_wrapup_finish): Rename.
+       (composite_print, m4_print_token): Adjust callers.
+       * m4/macro.c (m4_wrap_args, collect_arguments): Allow builtin
+       tokens.
+       (m4__arg_print): Alter signature.
+       (trace_prepre, trace_pre, m4_arg_text, m4_arg_equal): Adjust
+       callers.
+       * modules/m4.c (m4wrap): Allow builtin tokens.
+       (defn, errprint): Adjust callers.
+       * modules/gnu.c (builtin): Likewise.
+       * doc/m4.texinfo (M4wrap): New test.
+       (Debuglen): Adjust expected output.
+
        Stage 21a: Optimize checks for end of input.
        Create a new polymorphic input block type, which always fails with
        CHAR_EOF, so that remaining input routines no longer have to check
diff --git a/doc/m4.texinfo b/doc/m4.texinfo
index 9198a09..d0bbfa4 100644
--- a/doc/m4.texinfo
+++ b/doc/m4.texinfo
@@ -4103,7 +4103,7 @@ echo(`1', `long string')
 @error{}m4trace: -1- echo(`1', `long s...') -> ``1',`l...'
 @result{}1,long string
 echo(defn(`changequote'))
address@hidden: -2- defn(`change...') -> <changequote>
address@hidden: -2- defn(`change...') -> `<changequote>'
 @error{}m4trace: -1- echo(<changequote>) -> ``<changequote>''
 @result{}
 debuglen
@@ -5352,6 +5352,24 @@ m4wrap(`m4wrap(`)')len(abc')
 @error{}m4:stdin:1: len: end of file in argument list
 @end example
 
+As of M4 1.6, @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
 
@@ -6193,7 +6211,7 @@ names (consider the beta release @samp{1.9b}, or the use 
of
 be used via @code{defn} rather than directly invoked.
 
 @comment This test is excluded from the testsuite since it depends on a
address@hidden texinfo macro; but builtin.at covers the same thing.
address@hidden texinfo macro; but builtins.at covers the same thing.
 @comment ignore
 @example
 defn(`__m4_version__')
diff --git a/m4/builtin.c b/m4/builtin.c
index 5228abc..04f5ee3 100644
--- a/m4/builtin.c
+++ b/m4/builtin.c
@@ -88,10 +88,12 @@ m4_builtin_find_by_func (m4_module *module, m4_builtin_func 
*func)
 
 /* Print a representation of FUNC to OBS, optionally including the
    MODULE it came from.  If FLATTEN, output QUOTES around an empty
-   string instead.  */
+   string; if CHAIN, append the builtin to the chain; otherwise print
+   the name of FUNC.  */
 void
-m4_builtin_print (m4_obstack *obs, const m4_builtin *func, bool flatten,
-                 const m4_string_pair *quotes, m4_module *module)
+m4__builtin_print (m4_obstack *obs, const m4__builtin *func, bool flatten,
+                  m4__symbol_chain **chain, const m4_string_pair *quotes,
+                  bool module)
 {
   assert (func);
   if (flatten)
@@ -101,17 +103,19 @@ m4_builtin_print (m4_obstack *obs, const m4_builtin 
*func, bool flatten,
          obstack_grow (obs, quotes->str1, quotes->len1);
          obstack_grow (obs, quotes->str2, quotes->len2);
        }
-      module = NULL;
+      module = false;
     }
+  else if (chain)
+    m4__append_builtin (obs, func, NULL, chain);
   else
     {
       obstack_1grow (obs, '<');
-      obstack_grow (obs, func->name, strlen (func->name));
+      obstack_grow (obs, func->builtin.name, strlen (func->builtin.name));
       obstack_1grow (obs, '>');
     }
   if (module)
     {
-      const char *text = m4_get_module_name (module);
+      const char *text = m4_get_module_name (func->module);
       obstack_1grow (obs, '{');
       obstack_grow (obs, text, strlen (text));
       obstack_1grow (obs, '}');
diff --git a/m4/input.c b/m4/input.c
index 3607fa7..0f48768 100644
--- a/m4/input.c
+++ b/m4/input.c
@@ -60,7 +60,7 @@
    "wrapup_stack" to "current_input" can continue indefinitely, even
    generating infinite loops (e.g. "define(`f',`m4wrap(`f')')f"),
    without memory leaks.  Adding wrapped data is done through
-   m4_push_wrapup_init/m4_push_wrapup_finish().
+   m4__push_wrapup_init/m4__push_wrapup_finish().
 
    Pushing new input on the input stack is done by m4_push_file(), the
    conceptual m4_push_string(), and m4_push_builtin() (for builtin
@@ -98,11 +98,6 @@ static       int     file_read               (m4_input_block 
*, m4 *, bool, bool,
 static void    file_unget              (m4_input_block *, int);
 static bool    file_clean              (m4_input_block *, m4 *, bool);
 static void    file_print              (m4_input_block *, m4 *, m4_obstack *);
-static int     builtin_peek            (m4_input_block *, m4 *, bool);
-static int     builtin_read            (m4_input_block *, m4 *, bool, bool,
-                                        bool);
-static void    builtin_unget           (m4_input_block *, int);
-static void    builtin_print           (m4_input_block *, m4 *, m4_obstack *);
 static int     string_peek             (m4_input_block *, m4 *, bool);
 static int     string_read             (m4_input_block *, m4 *, bool, bool,
                                         bool);
@@ -190,7 +185,6 @@ struct m4_input_block
          bool_bitfield line_start : 1; /* Saved start_of_input_line state.  */
        }
       u_f;     /* See file_funcs.  */
-      const m4__builtin *builtin;      /* A builtin, see builtin_funcs.  */
       struct
        {
          m4__symbol_chain *chain;      /* Current link in chain.  */
@@ -238,11 +232,6 @@ static struct input_funcs file_funcs = {
   file_peek, file_read, file_unget, file_clean, file_print
 };
 
-/* Vtable for handling input from builtin functions.  */
-static struct input_funcs builtin_funcs = {
-  builtin_peek, builtin_read, builtin_unget, NULL, builtin_print
-};
-
 /* Vtable for handling input from strings.  */
 static struct input_funcs string_funcs = {
   string_peek, string_read, string_unget, NULL, string_print
@@ -399,66 +388,6 @@ m4_push_file (m4 *context, FILE *fp, const char *title, 
bool close_file)
 }
 
 
-/* Handle a builtin macro token.  */
-static int
-builtin_peek (m4_input_block *me, m4 *context M4_GNUC_UNUSED,
-             bool allow_argv M4_GNUC_UNUSED)
-{
-  return me->u.builtin ? CHAR_BUILTIN : CHAR_RETRY;
-}
-
-static int
-builtin_read (m4_input_block *me, m4 *context M4_GNUC_UNUSED,
-             bool allow_quote M4_GNUC_UNUSED, bool allow_argv M4_GNUC_UNUSED,
-             bool allow_unget M4_GNUC_UNUSED)
-{
-  /* Not consumed here - wait until init_builtin_token.  */
-  return me->u.builtin ? CHAR_BUILTIN : CHAR_RETRY;
-}
-
-static void
-builtin_unget (m4_input_block *me, int ch)
-{
-  assert (ch == CHAR_BUILTIN && me->u.builtin);
-}
-
-static void
-builtin_print (m4_input_block *me, m4 *context, m4_obstack *obs)
-{
-  bool module = m4_is_debug_bit (context, M4_DEBUG_TRACE_MODULE);
-  m4_builtin_print (obs, &me->u.builtin->builtin, false, NULL,
-                   module ? me->u.builtin->module : NULL);
-}
-
-/* m4_push_builtin () pushes TOKEN, which contains a builtin's
-   definition, on the input stack.  If next is non-NULL, this push
-   invalidates a call to m4_push_string_init (), whose storage is
-   consequently released.  */
-void
-m4_push_builtin (m4 *context, m4_symbol_value *token)
-{
-  m4_input_block *i;
-
-  /* Make sure we were passed a builtin function type token.  */
-  assert (m4_is_symbol_value_func (token));
-
-  if (next != NULL)
-    {
-      obstack_free (current_input, next);
-      next = NULL;
-    }
-
-  i = (m4_input_block *) obstack_alloc (current_input, sizeof *i);
-  i->funcs = &builtin_funcs;
-  i->file = m4_get_current_file (context);
-  i->line = m4_get_current_line (context);
-  i->u.builtin = token->u.builtin;
-  i->prev = isp;
-  isp = i;
-  input_change = true;
-}
-
-
 /* Handle string expansion text.  */
 static int
 string_peek (m4_input_block *me, m4 *context M4_GNUC_UNUSED,
@@ -560,7 +489,18 @@ m4__push_symbol (m4 *context, m4_symbol_value *value, 
size_t level, bool inuse)
          return false;
        }
     }
-  else if (!m4_is_symbol_value_func (value))
+  else if (m4_is_symbol_value_func (value))
+    {
+      if (next->funcs == &string_funcs)
+       {
+         next->funcs = &composite_funcs;
+         next->u.u_c.chain = next->u.u_c.end = NULL;
+       }
+      m4__append_builtin (current_input, value->u.builtin, &next->u.u_c.chain,
+                         &next->u.u_c.end);
+      return false;
+    }
+  else
     {
       /* For composite values, if argv is already in use, creating
         additional references for long text segments is more
@@ -607,24 +547,15 @@ m4__push_symbol (m4 *context, m4_symbol_value *value, 
size_t level, bool inuse)
       m4__adjust_refcount (context, level, true);
       inuse = true;
     }
-  else if (m4_is_symbol_value_func (value))
-    {
-      chain = (m4__symbol_chain *) obstack_alloc (current_input,
-                                                 sizeof *chain);
-      if (next->u.u_c.end)
-       next->u.u_c.end->next = chain;
-      else
-       next->u.u_c.chain = chain;
-      next->u.u_c.end = chain;
-      chain->next = NULL;
-      chain->type = M4__CHAIN_FUNC;
-      chain->quote_age = 0;
-      chain->u.builtin = value->u.builtin;
-    }
   while (src_chain)
     {
-      /* TODO - support func concatenation.  */
-      assert (src_chain->type != M4__CHAIN_FUNC);
+      if (src_chain->type == M4__CHAIN_FUNC)
+       {
+         m4__append_builtin (current_input, src_chain->u.builtin,
+                             &next->u.u_c.chain, &next->u.u_c.end);
+         src_chain = src_chain->next;
+         continue;
+       }
       if (level == SIZE_MAX)
        {
          /* Nothing to copy, since link already lives on obstack.  */
@@ -935,17 +866,16 @@ composite_print (m4_input_block *me, m4 *context, 
m4_obstack *obs)
            done = true;
          break;
        case M4__CHAIN_FUNC:
-         m4_builtin_print (obs, &chain->u.builtin->builtin, false, NULL,
-                           module ? chain->u.builtin->module : NULL);
+         m4__builtin_print (obs, chain->u.builtin, false, NULL, NULL, module);
          break;
        case M4__CHAIN_ARGV:
          assert (!chain->u.u_a.comma);
-         if (m4_arg_print (context, obs, chain->u.u_a.argv,
-                           chain->u.u_a.index,
-                           m4__quote_cache (M4SYNTAX, NULL, chain->quote_age,
-                                            chain->u.u_a.quotes),
-                           chain->u.u_a.flatten, NULL, &maxlen, false,
-                           module))
+         if (m4__arg_print (context, obs, chain->u.u_a.argv,
+                            chain->u.u_a.index,
+                            m4__quote_cache (M4SYNTAX, NULL, chain->quote_age,
+                                             chain->u.u_a.quotes),
+                            chain->u.u_a.flatten, NULL, NULL, &maxlen, false,
+                            module))
            done = true;
          break;
        default:
@@ -987,6 +917,46 @@ m4__make_text_link (m4_obstack *obs, m4__symbol_chain 
**start,
     }
 }
 
+/* Given an obstack OBS, capture any unfinished text as a link, then
+   append the builtin FUNC as the next link in the chain that starts
+   at *START and ends at *END.  START may be NULL if *END is
+   non-NULL.  */
+void
+m4__append_builtin (m4_obstack *obs, const m4__builtin *func,
+                   m4__symbol_chain **start, m4__symbol_chain **end)
+{
+  m4__symbol_chain *chain;
+
+  assert (func);
+  m4__make_text_link (obs, start, end);
+  chain = (m4__symbol_chain *) obstack_alloc (obs, sizeof *chain);
+  if (*end)
+    (*end)->next = chain;
+  else
+    *start = chain;
+  *end = chain;
+  chain->next = NULL;
+  chain->type = M4__CHAIN_FUNC;
+  chain->quote_age = 0;
+  chain->u.builtin = func;
+}
+
+/* Push TOKEN, which contains a builtin's definition, onto the obstack
+   OBS, which is either input stack or the wrapup stack.  */
+void
+m4_push_builtin (m4 *context, m4_obstack *obs, m4_symbol_value *token)
+{
+  m4_input_block *i = (obs == current_input ? next : wsp);
+  assert (i);
+  if (i->funcs == &string_funcs)
+    {
+      i->funcs = &composite_funcs;
+      i->u.u_c.chain = i->u.u_c.end = NULL;
+    }
+  else
+    assert (i->funcs == &composite_funcs);
+  m4__append_builtin (obs, token->u.builtin, &i->u.u_c.chain, &i->u.u_c.end);
+}
 
 
 /* End of input optimization.  By providing these dummy callback
@@ -1038,13 +1008,12 @@ m4_input_print (m4 *context, m4_obstack *obs, 
m4_input_block *input)
     }
 }
 
-/* The function m4_push_wrapup_init () returns an obstack ready for
-   direct expansion of wrapup text, and should be followed by
-   m4_push_wrapup_finish ().
-
-   FIXME - we should allow pushing builtins as well as text.  */
+/* Return an obstack ready for direct expansion of wrapup text, and
+   set *END to the location that should be updated if any builtin
+   tokens are wrapped.  This should be followed by
+   m4__push_wrapup_finish ().  */
 m4_obstack *
-m4_push_wrapup_init (m4 *context)
+m4__push_wrapup_init (m4 *context, m4__symbol_chain ***end)
 {
   m4_input_block *i;
   m4__symbol_chain *chain;
@@ -1077,12 +1046,13 @@ m4_push_wrapup_init (m4 *context)
   chain->quote_age = 0;
   chain->u.u_l.file = m4_get_current_file (context);
   chain->u.u_l.line = m4_get_current_line (context);
+  *end = &i->u.u_c.end;
   return wrapup_stack;
 }
 
 /* After pushing wrapup text, this completes the bookkeeping.  */
 void
-m4_push_wrapup_finish (void)
+m4__push_wrapup_finish (void)
 {
   m4__make_text_link (wrapup_stack, &wsp->u.u_c.chain, &wsp->u.u_c.end);
   assert (wsp->u.u_c.end->type != M4__CHAIN_LOC);
@@ -1157,24 +1127,14 @@ m4_pop_wrapup (m4 *context)
 static void
 init_builtin_token (m4 *context, m4_symbol_value *token)
 {
-  if (isp->funcs == &builtin_funcs)
-    {
-      assert (isp->u.builtin);
-      if (token)
-       m4__set_symbol_value_builtin (token, isp->u.builtin);
-      isp->u.builtin = NULL;
-    }
-  else
-    {
-      m4__symbol_chain *chain;
-      assert (isp->funcs == &composite_funcs);
-      chain = isp->u.u_c.chain;
-      assert (!chain->quote_age && chain->type == M4__CHAIN_FUNC
-             && chain->u.builtin);
-      if (token)
-       m4__set_symbol_value_builtin (token, chain->u.builtin);
-      chain->u.builtin = NULL;
-    }
+  m4__symbol_chain *chain;
+  assert (isp->funcs == &composite_funcs);
+  chain = isp->u.u_c.chain;
+  assert (!chain->quote_age && chain->type == M4__CHAIN_FUNC
+         && chain->u.builtin);
+  if (token)
+    m4__set_symbol_value_builtin (token, chain->u.builtin);
+  chain->u.builtin = NULL;
 }
 
 /* When a QUOTE token is seen, convert VALUE to a composite (if it is
@@ -1984,7 +1944,8 @@ m4_print_token (m4 *context, const char *s, 
m4__token_type type,
   if (token)
     {
       obstack_init (&obs);
-      m4_symbol_value_print (context, token, &obs, NULL, false, NULL, true);
+      m4__symbol_value_print (context, token, &obs, NULL, false, NULL, NULL,
+                             true);
       len = obstack_object_size (&obs);
       xfprintf (stderr, "%s\n", quotearg_style_mem (c_maybe_quoting_style,
                                                    obstack_finish (&obs),
diff --git a/m4/m4module.h b/m4/m4module.h
index 5c1f4e8..ced18d5 100644
--- a/m4/m4module.h
+++ b/m4/m4module.h
@@ -284,9 +284,6 @@ extern m4_symbol_value *m4_get_symbol_value   (m4_symbol*);
 extern bool            m4_get_symbol_traced      (m4_symbol*);
 extern bool            m4_set_symbol_name_traced (m4_symbol_table*,
                                                   const char *, bool);
-extern bool    m4_symbol_value_print   (m4 *, m4_symbol_value *, m4_obstack *,
-                                        const m4_string_pair *, bool,
-                                        size_t *, bool);
 extern void    m4_symbol_print         (m4 *, m4_symbol *, m4_obstack *,
                                         const m4_string_pair *, bool, size_t,
                                         bool);
@@ -343,8 +340,6 @@ extern void         m4_set_symbol_value_placeholder 
(m4_symbol_value *,
 extern m4_symbol_value *m4_builtin_find_by_name (m4_module *, const char *);
 extern m4_symbol_value *m4_builtin_find_by_func (m4_module *,
                                                  m4_builtin_func *);
-extern void m4_builtin_print (m4_obstack *, const m4_builtin *, bool,
-                             const m4_string_pair *, m4_module *);
 
 
 
@@ -364,9 +359,6 @@ extern bool m4_arg_empty            (m4_macro_args *, 
size_t);
 extern size_t  m4_arg_len              (m4 *, m4_macro_args *, size_t);
 extern m4_builtin_func *m4_arg_func    (m4_macro_args *, size_t);
 extern m4_obstack *m4_arg_scratch      (m4 *);
-extern bool    m4_arg_print            (m4 *, m4_obstack *, m4_macro_args *,
-                                        size_t, const m4_string_pair *, bool,
-                                        const char *, size_t *, bool, bool);
 extern m4_macro_args *m4_make_argv_ref (m4 *, m4_macro_args *, const char *,
                                         size_t, bool, bool);
 extern void    m4_push_arg             (m4 *, m4_obstack *, m4_macro_args *,
@@ -501,11 +493,9 @@ extern     void    m4_skip_line    (m4 *context, const 
char *);
 /* push back input */
 
 extern void    m4_push_file    (m4 *, FILE *, const char *, bool);
-extern void    m4_push_builtin (m4 *, m4_symbol_value *);
+extern void    m4_push_builtin (m4 *, m4_obstack *, m4_symbol_value *);
 extern m4_obstack      *m4_push_string_init    (m4 *);
 extern m4_input_block  *m4_push_string_finish  (void);
-extern m4_obstack      *m4_push_wrapup_init    (m4 *);
-extern void    m4_push_wrapup_finish           (void);
 extern bool    m4_pop_wrapup   (m4 *);
 extern void    m4_input_print  (m4 *, m4_obstack *, m4_input_block *);
 
diff --git a/m4/m4private.h b/m4/m4private.h
index 86f18e8..48a0075 100644
--- a/m4/m4private.h
+++ b/m4/m4private.h
@@ -28,6 +28,7 @@
 
 typedef struct m4__search_path_info m4__search_path_info;
 typedef struct m4__macro_arg_stacks m4__macro_arg_stacks;
+typedef struct m4__symbol_chain m4__symbol_chain;
 
 typedef enum {
   M4_SYMBOL_VOID,              /* Traced but undefined, u is invalid.  */
@@ -163,6 +164,9 @@ typedef struct m4__builtin m4__builtin;
 
 extern void m4__set_symbol_value_builtin (m4_symbol_value *,
                                          const m4__builtin *);
+extern void m4__builtin_print (m4_obstack *, const m4__builtin *, bool,
+                              m4__symbol_chain **, const m4_string_pair *,
+                              bool);
 
 
 /* --- MODULE MANAGEMENT --- */
@@ -198,8 +202,6 @@ extern m4_module *  m4__module_find (const char *name);
 
 /* --- SYMBOL TABLE MANAGEMENT --- */
 
-typedef struct m4__symbol_chain m4__symbol_chain;
-
 struct m4_symbol
 {
   bool traced;                 /* True if this symbol is traced.  */
@@ -334,6 +336,10 @@ extern size_t      m4__adjust_refcount     (m4 *, size_t, 
bool);
 extern bool    m4__arg_adjust_refcount (m4 *, m4_macro_args *, bool);
 extern void    m4__push_arg_quote      (m4 *, m4_obstack *, m4_macro_args *,
                                         size_t, const m4_string_pair *);
+extern bool    m4__arg_print           (m4 *, m4_obstack *, m4_macro_args *,
+                                        size_t, const m4_string_pair *, bool,
+                                        m4__symbol_chain **, const char *,
+                                        size_t *, bool, bool);
 
 #define VALUE_NEXT(T)          ((T)->next)
 #define VALUE_MODULE(T)                ((T)->module)
@@ -418,7 +424,9 @@ struct m4_symbol_arg {
 
 extern void m4__symtab_remove_module_references (m4_symbol_table*,
                                                 m4_module *);
-
+extern bool m4__symbol_value_print (m4 *, m4_symbol_value *, m4_obstack *,
+                                   const m4_string_pair *, bool,
+                                   m4__symbol_chain **, size_t *, bool);
 
 
 
@@ -530,8 +538,13 @@ typedef enum {
 
 extern void            m4__make_text_link (m4_obstack *, m4__symbol_chain **,
                                            m4__symbol_chain **);
+extern void            m4__append_builtin (m4_obstack *, const m4__builtin *,
+                                           m4__symbol_chain **,
+                                           m4__symbol_chain **);
 extern bool            m4__push_symbol (m4 *, m4_symbol_value *, size_t,
                                         bool);
+extern m4_obstack      *m4__push_wrapup_init (m4 *, m4__symbol_chain ***);
+extern void            m4__push_wrapup_finish (void);
 extern m4__token_type  m4__next_token (m4 *, m4_symbol_value *, int *,
                                        m4_obstack *, bool, const char *);
 extern bool            m4__next_token_is_open (m4 *);
diff --git a/m4/macro.c b/m4/macro.c
index 6d1976d..3072444 100644
--- a/m4/macro.c
+++ b/m4/macro.c
@@ -663,8 +663,7 @@ collect_arguments (m4 *context, const char *name, size_t 
len,
   argv->wrapper = args.wrapper;
   argv->has_ref = args.has_ref;
   argv->has_func = args.has_func;
-  /* TODO allow funcs without crippling quote age.  */
-  if (args.quote_age != m4__quote_age (M4SYNTAX) || args.has_func)
+  if (args.quote_age != m4__quote_age (M4SYNTAX))
     argv->quote_age = 0;
   argv->arraylen = args.arraylen;
   return argv;
@@ -928,8 +927,8 @@ trace_prepre (m4 *context, const char *name, size_t id, 
m4_symbol_value *value)
     quotes = m4_get_syntax_quotes (M4SYNTAX);
   trace_header (context, id);
   trace_format (context, "%s ... = ", name);
-  m4_symbol_value_print (context, value, &context->trace_messages, quotes,
-                        false, &arg_length, module);
+  m4__symbol_value_print (context, value, &context->trace_messages, quotes,
+                         false, NULL, &arg_length, module);
   trace_flush (context);
 }
 
@@ -950,8 +949,8 @@ trace_pre (m4 *context, size_t id, m4_macro_args *argv)
       if (m4_is_debug_bit (context, M4_DEBUG_TRACE_QUOTE))
        quotes = m4_get_syntax_quotes (M4SYNTAX);
       trace_format (context, "(");
-      m4_arg_print (context, &context->trace_messages, argv, 1, quotes, false,
-                   ", ", &arg_length, true, module);
+      m4__arg_print (context, &context->trace_messages, argv, 1, quotes, false,
+                    NULL, ", ", &arg_length, true, module);
       trace_format (context, ")");
     }
 }
@@ -1250,7 +1249,6 @@ m4_arg_text (m4 *context, m4_macro_args *argv, size_t 
index)
   value = m4_arg_symbol (argv, index);
   if (m4_is_symbol_value_text (value))
     return m4_get_symbol_value_text (value);
-  /* TODO - concatenate functions.  */
   assert (value->type == M4_SYMBOL_COMP);
   chain = value->u.u_c.chain;
   obs = m4_arg_scratch (context);
@@ -1262,11 +1260,11 @@ m4_arg_text (m4 *context, m4_macro_args *argv, size_t 
index)
          obstack_grow (obs, chain->u.u_s.str, chain->u.u_s.len);
          break;
        case M4__CHAIN_ARGV:
-         m4_arg_print (context, obs, chain->u.u_a.argv, chain->u.u_a.index,
-                       m4__quote_cache (M4SYNTAX, NULL, chain->quote_age,
-                                        chain->u.u_a.quotes),
-                       argv->flatten || chain->u.u_a.flatten, NULL, NULL,
-                       false, false);
+         m4__arg_print (context, obs, chain->u.u_a.argv, chain->u.u_a.index,
+                        m4__quote_cache (M4SYNTAX, NULL, chain->quote_age,
+                                         chain->u.u_a.quotes),
+                        argv->flatten || chain->u.u_a.flatten, NULL, NULL,
+                        NULL, false, false);
          break;
        default:
          assert (!"m4_arg_text");
@@ -1292,6 +1290,7 @@ m4_arg_equal (m4 *context, m4_macro_args *argv, size_t 
indexa, size_t indexb)
   m4__symbol_chain tmpb;
   m4__symbol_chain *ca = &tmpa;
   m4__symbol_chain *cb = &tmpb;
+  m4__symbol_chain *chain;
   m4_obstack *obs = m4_arg_scratch (context);
 
   /* Quick tests.  */
@@ -1350,34 +1349,33 @@ m4_arg_equal (m4 *context, m4_macro_args *argv, size_t 
indexa, size_t indexb)
     {
       if (ca->type == M4__CHAIN_ARGV)
        {
-         tmpa.next = ca->next;
+         tmpa.next = NULL;
          tmpa.type = M4__CHAIN_STR;
-         /* TODO support funcs in address@hidden  */
-         assert (!ca->u.u_a.has_func || argv->flatten || ca->u.u_a.flatten);
-         m4_arg_print (context, obs, ca->u.u_a.argv, ca->u.u_a.index,
-                       m4__quote_cache (M4SYNTAX, NULL, ca->quote_age,
-                                        ca->u.u_a.quotes),
-                       argv->flatten || ca->u.u_a.flatten, NULL, NULL, false,
-                       false);
-         tmpa.u.u_s.len = obstack_object_size (obs);
-         tmpa.u.u_s.str = (char *) obstack_finish (obs);
-         ca = &tmpa;
+         tmpa.u.u_s.str = NULL;
+         tmpa.u.u_s.len = 0;
+         chain = &tmpa;
+         m4__arg_print (context, obs, ca->u.u_a.argv, ca->u.u_a.index,
+                        m4__quote_cache (M4SYNTAX, NULL, ca->quote_age,
+                                         ca->u.u_a.quotes),
+                        argv->flatten || ca->u.u_a.flatten, &chain, NULL,
+                        NULL, false, false);
+         assert (obstack_object_size (obs) == 0 && chain != &tmpa);
+         chain->next = ca->next;
+         ca = tmpa.next;
          continue;
        }
       if (cb->type == M4__CHAIN_ARGV)
        {
-         tmpb.next = cb->next;
+         tmpb.next = NULL;
          tmpb.type = M4__CHAIN_STR;
-         /* TODO support funcs in address@hidden  */
-         assert (!cb->u.u_a.has_func || argv->flatten || cb->u.u_a.flatten);
-         m4_arg_print (context, obs, cb->u.u_a.argv, cb->u.u_a.index,
-                       m4__quote_cache (M4SYNTAX, NULL, cb->quote_age,
-                                        cb->u.u_a.quotes),
-                       argv->flatten || cb->u.u_a.flatten, NULL, NULL, false,
-                       false);
-         tmpb.u.u_s.len = obstack_object_size (obs);
-         tmpb.u.u_s.str = (char *) obstack_finish (obs);
-         cb = &tmpb;
+         m4__arg_print (context, obs, cb->u.u_a.argv, cb->u.u_a.index,
+                        m4__quote_cache (M4SYNTAX, NULL, cb->quote_age,
+                                         cb->u.u_a.quotes),
+                        argv->flatten || cb->u.u_a.flatten, &chain, NULL,
+                        NULL, false, false);
+         assert (obstack_object_size (obs) == 0 && chain != &tmpb);
+         chain->next = cb->next;
+         cb = tmpb.next;
          continue;
        }
       if (ca->type == M4__CHAIN_FUNC)
@@ -1504,20 +1502,23 @@ m4_arg_func (m4_macro_args *argv, size_t index)
 
 /* Dump a representation of ARGV to the obstack OBS, starting with
    argument INDEX.  If QUOTES is non-NULL, each argument is displayed
-   with those quotes.  If FLATTEN, builtins are ignored.  Separate
-   arguments with SEP, which defaults to a comma.  If MAX_LEN is
-   non-NULL, truncate the output after *MAX_LEN bytes are output and
-   return true; otherwise, return false, and reduce *MAX_LEN by the
-   number of bytes output.  If QUOTE_EACH, the truncation length is
-   reset for each argument, quotes do not count against length, and
-   all arguments are printed; otherwise, quotes count against the
-   length and trailing arguments may be discarded.  If MODULE, print
-   any details about originating modules; modules do not count against
-   truncation length.  */
+   with those quotes.  If FLATTEN, builtins are converted to empty
+   quotes; if CHAINP, *CHAINP is updated with macro tokens; otherwise,
+   builtins are represented by their name.  Separate arguments with
+   SEP, which defaults to a comma.  If MAX_LEN is non-NULL, truncate
+   the output after *MAX_LEN bytes are output and return true;
+   otherwise, return false, and reduce *MAX_LEN by the number of bytes
+   output.  If QUOTE_EACH, the truncation length is reset for each
+   argument, quotes do not count against length, and all arguments are
+   printed; otherwise, quotes count against the length and trailing
+   arguments may be discarded.  If MODULE, print any details about
+   originating modules; modules do not count against truncation
+   length.  MAX_LEN and CHAINP may not both be specified.  */
 bool
-m4_arg_print (m4 *context, m4_obstack *obs, m4_macro_args *argv, size_t index,
-             const m4_string_pair *quotes, bool flatten, const char *sep,
-             size_t *max_len, bool quote_each, bool module)
+m4__arg_print (m4 *context, m4_obstack *obs, m4_macro_args *argv, size_t index,
+              const m4_string_pair *quotes, bool flatten,
+              m4__symbol_chain **chainp, const char *sep, size_t *max_len,
+              bool quote_each, bool module)
 {
   size_t len = max_len ? *max_len : SIZE_MAX;
   size_t i;
@@ -1525,6 +1526,8 @@ m4_arg_print (m4 *context, m4_obstack *obs, m4_macro_args 
*argv, size_t index,
   size_t sep_len;
   size_t *plen = quote_each ? NULL : &len;
 
+  if (chainp)
+    assert (!max_len && *chainp);
   if (!sep)
     sep = ",";
   sep_len = strlen (sep);
@@ -1539,9 +1542,9 @@ m4_arg_print (m4 *context, m4_obstack *obs, m4_macro_args 
*argv, size_t index,
          && m4_shipout_string_trunc (obs, quotes->str1, quotes->len1, NULL,
                                      plen))
        return true;
-      if (m4_symbol_value_print (context, arg_symbol (argv, i, NULL, flatten),
-                                obs, quote_each ? quotes : NULL, flatten,
-                                &len, module))
+      if (m4__symbol_value_print (context, arg_symbol (argv, i, NULL, flatten),
+                                 obs, quote_each ? quotes : NULL, flatten,
+                                 chainp, &len, module))
        return true;
       if (quotes && !quote_each
          && m4_shipout_string_trunc (obs, quotes->str2, quotes->len2, NULL,
@@ -1550,6 +1553,8 @@ m4_arg_print (m4 *context, m4_obstack *obs, m4_macro_args 
*argv, size_t index,
     }
   if (max_len)
     *max_len = len;
+  else if (chainp)
+    m4__make_text_link (obs, NULL, chainp);
   return false;
 }
 
@@ -1680,12 +1685,13 @@ m4_wrap_args (m4 *context, m4_macro_args *argv)
   m4_obstack *obs;
   m4_symbol_value *value;
   m4__symbol_chain *chain;
+  m4__symbol_chain **end;
   size_t limit = m4_get_posixly_correct_opt (context) ? 2 : argv->argc;
 
   if (limit == 2 && m4_arg_empty (argv, 1))
     return;
 
-  obs = m4_push_wrapup_init (context);
+  obs = m4__push_wrapup_init (context, &end);
   for (i = 1; i < limit; i++)
     {
       if (i != 1)
@@ -1698,8 +1704,7 @@ m4_wrap_args (m4 *context, m4_macro_args *argv)
                        m4_get_symbol_value_len (value));
          break;
        case M4_SYMBOL_FUNC:
-         /* TODO allow builtins.  */
-         assert (false);
+         m4__append_builtin (obs, value->u.builtin, NULL, end);
          break;
        case M4_SYMBOL_COMP:
          chain = value->u.u_c.chain;
@@ -1711,17 +1716,16 @@ m4_wrap_args (m4 *context, m4_macro_args *argv)
                  obstack_grow (obs, chain->u.u_s.str, chain->u.u_s.len);
                  break;
                case M4__CHAIN_FUNC:
-                 /* TODO allow builtins.  */
-                 assert (false);
+                 m4__append_builtin (obs, chain->u.builtin, NULL, end);
                  break;
                case M4__CHAIN_ARGV:
-                 m4_arg_print (context, obs, chain->u.u_a.argv,
-                               chain->u.u_a.index,
-                               m4__quote_cache (M4SYNTAX, NULL,
-                                                chain->quote_age,
-                                                chain->u.u_a.quotes),
-                               chain->u.u_a.flatten, NULL, NULL, false,
-                               false);
+                 m4__arg_print (context, obs, chain->u.u_a.argv,
+                                chain->u.u_a.index,
+                                m4__quote_cache (M4SYNTAX, NULL,
+                                                 chain->quote_age,
+                                                 chain->u.u_a.quotes),
+                                chain->u.u_a.flatten, end, NULL, NULL, false,
+                                false);
                  break;
                default:
                  assert (!"m4_wrap_args");
@@ -1735,7 +1739,7 @@ m4_wrap_args (m4 *context, m4_macro_args *argv)
          abort ();
        }
     }
-  m4_push_wrapup_finish ();
+  m4__push_wrapup_finish ();
 }
 
 
diff --git a/m4/symtab.c b/m4/symtab.c
index 7fa7ccc..f4bb855 100644
--- a/m4/symtab.c
+++ b/m4/symtab.c
@@ -543,16 +543,17 @@ m4_set_symbol_name_traced (m4_symbol_table *symtab, const 
char *name,
 }
 
 /* Grow OBS with a text representation of VALUE.  If QUOTES, then use
-   it to surround a text definition.  If FLATTEN, then flatten builtin
-   macros to the empty string.  If MAXLEN, then truncate text
-   definitions to *MAXLEN, and adjust by how many characters are
-   printed.  If MODULE, then include which module defined a builtin.
-   Return true if the output was truncated.  QUOTES and MODULE do not
-   count against the truncation length.  */
+   it to surround a text definition.  If FLATTEN, builtins are
+   converted to empty quotes; if CHAINP, *CHAINP is updated with macro
+   tokens; otherwise, builtins are represented by their name.  If
+   MAXLEN, then truncate text definitions to *MAXLEN, and adjust by
+   how many characters are printed.  If MODULE, then include which
+   module defined a builtin.  Return true if the output was truncated.
+   QUOTES and MODULE do not count against the truncation length.  */
 bool
-m4_symbol_value_print (m4 *context, m4_symbol_value *value, m4_obstack *obs,
-                      const m4_string_pair *quotes, bool flatten,
-                      size_t *maxlen, bool module)
+m4__symbol_value_print (m4 *context, m4_symbol_value *value, m4_obstack *obs,
+                       const m4_string_pair *quotes, bool flatten,
+                       m4__symbol_chain **chainp, size_t *maxlen, bool module)
 {
   const char *text;
   m4__symbol_chain *chain;
@@ -568,8 +569,8 @@ m4_symbol_value_print (m4 *context, m4_symbol_value *value, 
m4_obstack *obs,
        result = true;
       break;
     case M4_SYMBOL_FUNC:
-      m4_builtin_print (obs, m4_get_symbol_value_builtin (value), flatten,
-                       quotes, module ? VALUE_MODULE (value) : NULL);
+      m4__builtin_print (obs, value->u.builtin, flatten, chainp, quotes,
+                        module);
       module = false;
       break;
     case M4_SYMBOL_PLACEHOLDER:
@@ -607,22 +608,21 @@ m4_symbol_value_print (m4 *context, m4_symbol_value 
*value, m4_obstack *obs,
                result = true;
              break;
            case M4__CHAIN_FUNC:
-             m4_builtin_print (obs, &chain->u.builtin->builtin, flatten,
-                               quotes,
-                               module ? chain->u.builtin->module : NULL);
+             m4__builtin_print (obs, chain->u.builtin, flatten, chainp,
+                                quotes, module);
              break;
            case M4__CHAIN_ARGV:
-             if (m4_arg_print (context, obs, chain->u.u_a.argv,
-                               chain->u.u_a.index,
-                               m4__quote_cache (M4SYNTAX, NULL,
-                                                chain->quote_age,
-                                                chain->u.u_a.quotes),
-                               chain->u.u_a.flatten, NULL, &len, false,
-                               module))
+             if (m4__arg_print (context, obs, chain->u.u_a.argv,
+                                chain->u.u_a.index,
+                                m4__quote_cache (M4SYNTAX, NULL,
+                                                 chain->quote_age,
+                                                 chain->u.u_a.quotes),
+                                chain->u.u_a.flatten, chainp, NULL, &len,
+                                false, module))
                result = true;
              break;
            default:
-             assert (!"m4_symbol_value_print");
+             assert (!"m4__symbol_value_print");
              abort ();
            }
            chain = chain->next;
@@ -631,7 +631,7 @@ m4_symbol_value_print (m4 *context, m4_symbol_value *value, 
m4_obstack *obs,
        obstack_grow (obs, quotes->str2, quotes->len2);
       break;
     default:
-      assert (!"m4_symbol_value_print");
+      assert (!"m4__symbol_value_print");
       abort ();
     }
 
@@ -665,7 +665,8 @@ m4_symbol_print (m4 *context, m4_symbol *symbol, m4_obstack 
*obs,
   assert (obs);
 
   value = m4_get_symbol_value (symbol);
-  m4_symbol_value_print (context, value, obs, quotes, false, &len, module);
+  m4__symbol_value_print (context, value, obs, quotes, false, NULL, &len,
+                         module);
   if (stack)
     {
       value = VALUE_NEXT (value);
@@ -674,8 +675,8 @@ m4_symbol_print (m4 *context, m4_symbol *symbol, m4_obstack 
*obs,
          obstack_1grow (obs, ',');
          obstack_1grow (obs, ' ');
          len = arg_length;
-         m4_symbol_value_print (context, value, obs, quotes, false, &len,
-                                module);
+         m4__symbol_value_print (context, value, obs, quotes, false, NULL,
+                                 &len, module);
          value = VALUE_NEXT (value);
        }
     }
@@ -873,7 +874,8 @@ dump_symbol_CB (m4_symbol_table *symtab, const char *name,
     {
       m4_obstack obs;
       obstack_init (&obs);
-      m4_symbol_value_print (context, value, &obs, NULL, false, NULL, true);
+      m4__symbol_value_print (context, value, &obs, NULL, false, NULL, NULL,
+                             true);
       xfprintf (stderr, "%s", (char *) obstack_finish (&obs));
       obstack_free (&obs, NULL);
     }
diff --git a/modules/gnu.c b/modules/gnu.c
index b481581..f0d3a44 100644
--- a/modules/gnu.c
+++ b/modules/gnu.c
@@ -424,7 +424,7 @@ M4BUILTIN_HANDLER (builtin)
            m4_warn (context, 0, me, _("undefined builtin `%s'"), name);
          else
            {
-             m4_push_builtin (context, value);
+             m4_push_builtin (context, obs, value);
              free (value);
            }
        }
diff --git a/modules/m4.c b/modules/m4.c
index d484f4d..f4013ef 100644
--- a/modules/m4.c
+++ b/modules/m4.c
@@ -78,7 +78,7 @@ extern void m4_make_temp     (m4 *context, m4_obstack *obs, 
const char *macro,
   BUILTIN (index,      false,  true,   true,   2,      2  )    \
   BUILTIN (len,                false,  true,   true,   1,      1  )    \
   BUILTIN (m4exit,     false,  false,  false,  0,      1  )    \
-  BUILTIN (m4wrap,     false,  true,   false,  1,      -1 )    \
+  BUILTIN (m4wrap,     true,   true,   false,  1,      -1 )    \
   BUILTIN (maketemp,   false,  true,   false,  1,      1  )    \
   BUILTIN (mkstemp,    false,  true,   false,  1,      1  )    \
   BUILTIN (popdef,     true,   true,   false,  1,      -1 )    \
@@ -364,7 +364,7 @@ M4BUILTIN_HANDLER (defn)
        m4_shipout_string (context, obs, m4_get_symbol_text (symbol),
                           m4_get_symbol_len (symbol), true);
       else if (m4_is_symbol_func (symbol))
-       m4_push_builtin (context, m4_get_symbol_value (symbol));
+       m4_push_builtin (context, obs, m4_get_symbol_value (symbol));
       else if (m4_is_symbol_placeholder (symbol))
        m4_warn (context, 0, M4ARG (i),
                 _("builtin `%s' requested by frozen file not found"),
@@ -767,15 +767,17 @@ M4BUILTIN_HANDLER (mkstemp)
 /* Print all arguments on standard error.  */
 M4BUILTIN_HANDLER (errprint)
 {
-  size_t len;
+  size_t i;
 
-  assert (obstack_object_size (obs) == 0);
-  m4_arg_print (context, obs, argv, 1, NULL, true, " ", NULL, false, false);
   m4_sysval_flush (context, false);
-  len = obstack_object_size (obs);
   /* The close_stdin module makes it safe to skip checking the return
-     value here.  */
-  fwrite (obstack_finish (obs), 1, len, stderr);
+     values here.  */
+  fwrite (M4ARG (1), 1, M4ARGLEN (1), stderr);
+  for (i = 2; i < m4_arg_argc (argv); i++)
+    {
+      fputc (' ', stderr);
+      fwrite (M4ARG (i), 1, M4ARGLEN (i), stderr);
+    }
   fflush (stderr);
 }
 
-- 
1.5.5

>From 6b67f52903f191b8e0b9c295182a8e6504fb4f96 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Thu, 6 Dec 2007 13:33:40 -0700
Subject: [PATCH] Stage 21: $@ concatenates builtins, m4wrap takes builtins.

* src/m4.h (append_macro): New prototype.
(push_macro, push_wrapup_init, arg_print, func_print): Alter
prototypes.
* src/input.c (INPUT_MACRO): Delete, covered by INPUT_CHAIN.
(INPUT_EOF): New input block type, for efficiency.
(struct input_block): Remove u.func member.
(input_eof): New input sentinel.
(append_macro): New function.
(push_macro, push_wrapup_init): Add parameter.
(push_token): Support builtin tokens.
(init_macro_token): Always use chain for builtin tokens.
(push_string_init, pop_input, pop_wrapup, input_print)
(peek_input, next_char, next_char_1, input_init): Adjust callers.
* src/macro.c (arg_equal, wrap_args): Handle builtin tokens.
(arg_print): Add parameter.
(collect_arguments, arg_type, arg_text): Adjust callers.
* src/builtin.c (m4_m4wrap): Handle builtin tokens.
(func_print): Add parameter.
(m4_defn): Allow pushing builtin alongside other text.
(m4_errprint): Adjust caller.
* src/debug.c (trace_pre): Likewise.
* doc/m4.texinfo (Defn, Ifelse, Debug Levels): Update tests to new
behavior.
(M4wrap): New test.

(cherry picked from commit 32d4bf4d447e0e252c809897b795b57b3bfd74de)

Signed-off-by: Eric Blake <address@hidden>
---
 .cvsignore     |    2 +-
 .gitignore     |    2 +-
 ChangeLog      |   35 ++++++++++
 doc/m4.texinfo |   84 +++++++++++++----------
 src/builtin.c  |   26 ++++---
 src/debug.c    |    2 +-
 src/input.c    |  203 +++++++++++++++++++++++++++-----------------------------
 src/m4.h       |   12 ++-
 src/macro.c    |   86 +++++++++++++-----------
 9 files changed, 253 insertions(+), 199 deletions(-)

diff --git a/.cvsignore b/.cvsignore
index 8fc44d6..493776c 100644
--- a/.cvsignore
+++ b/.cvsignore
@@ -17,6 +17,7 @@ configure.lineno
 COPYING
 depcomp
 gendocs.sh
+GNUmakefile
 gnupload
 INSTALL
 install-sh
@@ -30,4 +31,3 @@ stamp-h
 stamp-h1
 stamp-h.in
 tests
-GNUmakefile
diff --git a/.gitignore b/.gitignore
index b581eac..fae2fd3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,7 @@ COPYING
 CVS
 depcomp
 gendocs.sh
+GNUmakefile
 gnupload
 INSTALL
 install-sh
@@ -34,4 +35,3 @@ stamp-h
 stamp-h1
 stamp-h.in
 tests
-GNUmakefile
diff --git a/ChangeLog b/ChangeLog
index 8501d11..a9fc7f5 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,38 @@
+2008-04-14  Eric Blake  <address@hidden>
+
+       Stage 21: $@ concatenates builtins, m4wrap takes builtins.
+       Create a new input block type, which always fails with CHAR_EOF,
+       so that remaining input routines no longer have to check for NULL
+       input block.  Improve arg_print to handle builtin tokens when
+       printing to a known chain, rather than always flattening builtins,
+       allowing m4wrap and $@ references to handle embedded builtins.
+       Memory impact: none.
+       Speed impact: noticeable improvement, from fewer conditionals.
+       * src/m4.h (append_macro): New prototype.
+       (push_macro, push_wrapup_init, arg_print, func_print): Alter
+       prototypes.
+       * src/input.c (INPUT_MACRO): Delete, covered by INPUT_CHAIN.
+       (INPUT_EOF): New input block type, for efficiency.
+       (struct input_block): Remove u.func member.
+       (input_eof): New input sentinel.
+       (append_macro): New function.
+       (push_macro, push_wrapup_init): Add parameter.
+       (push_token): Support builtin tokens.
+       (init_macro_token): Always use chain for builtin tokens.
+       (push_string_init, pop_input, pop_wrapup, input_print)
+       (peek_input, next_char, next_char_1, input_init): Adjust callers.
+       * src/macro.c (arg_equal, wrap_args): Handle builtin tokens.
+       (arg_print): Add parameter.
+       (collect_arguments, arg_type, arg_text): Adjust callers.
+       * src/builtin.c (m4_m4wrap): Handle builtin tokens.
+       (func_print): Add parameter.
+       (m4_defn): Allow pushing builtin alongside other text.
+       (m4_errprint): Adjust caller.
+       * src/debug.c (trace_pre): Likewise.
+       * doc/m4.texinfo (Defn, Ifelse, Debug Levels): Update tests to new
+       behavior.
+       (M4wrap): New test.
+
 2008-04-11  Eric Blake  <address@hidden>
 
        Ensure --program-prefix doesn't regress.
diff --git a/doc/m4.texinfo b/doc/m4.texinfo
index 4a523c6..52fc77b 100644
--- a/doc/m4.texinfo
+++ b/doc/m4.texinfo
@@ -2281,10 +2281,11 @@ bar
 @result{}0
 @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.6, @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
 $ @kbd{m4 -d}
@@ -2293,13 +2294,12 @@ define(`a', `A')define(`AA', `b')
 traceon(`defn', `define')
 @result{}
 defn(`a', `divnum', `a')
address@hidden:stdin:3: Warning: defn: cannot concatenate builtin `divnum'
address@hidden: -1- defn(`a', `divnum', `a') -> ``A'`A''
address@hidden: -1- defn(`a', `divnum', `a') -> ``A'<divnum>`A''
 @result{}AA
 define(`mydivnum', defn(`divnum', `divnum'))mydivnum
address@hidden:stdin:4: Warning: defn: cannot concatenate builtin `divnum'
address@hidden:stdin:4: Warning: defn: cannot concatenate builtin `divnum'
address@hidden: -2- defn(`divnum', `divnum')
address@hidden: -2- defn(`divnum', `divnum') -> `<divnum><divnum>'
address@hidden:stdin:4: Warning: define: cannot concatenate builtin `divnum'
address@hidden:stdin:4: Warning: define: cannot concatenate builtin `divnum'
 @error{}m4trace: -1- define(`mydivnum', `')
 @result{}
 traceoff(`defn', `define')
@@ -2317,10 +2317,10 @@ define(`mydivnum', `a'defn(`divnum'))mydivnum
 define(`q', ``$@@'')
 @result{}
 define(`foo', q(`a', defn(`divnum')))foo
address@hidden:stdin:10: Warning: define: cannot quote builtin
address@hidden,
address@hidden:stdin:10: Warning: define: cannot concatenate builtins
address@hidden
 ifdef(`foo', `yes', `no')
address@hidden
address@hidden
 @end example
 
 @node Pushdef
@@ -2860,8 +2860,8 @@ ifelse(`-01234567890123456789', `-'e(long)`-', `yes', 
`no')
 @result{}no
 @end example
 
address@hidden It would be nice to pass builtin tokens through m4wrap, as well
address@hidden as allowing concatenation of builtins in ifelse and user macros.
address@hidden It would be nice to allow concatenation of builtins without
address@hidden using $@ handling.
 @example
 define(`e', `$@@')define(`q', ``$@@'')define(`u', `$*')
 @result{}
@@ -2881,33 +2881,25 @@ cmp(`q(defn(`defn'))', `q(`<defn>')')-fixme
 cmp(`q(defn(`defn'))', ``'')-fixme
 @error{}m4:stdin:7: Warning: ifelse: cannot quote builtin
 @result{}no-fixme
-cmp(`q(`1', `2', defn(`defn'))', `q(`1', `2', defn(`d'))')-fixme
address@hidden:stdin:8: Warning: ifelse: cannot quote builtin
address@hidden:stdin:8: Warning: ifelse: cannot quote builtin
address@hidden
-cmp(`q(`1', `2', defn(`defn'))', `q(`1', `2', `<defn>')')-fixme
address@hidden:stdin:9: Warning: ifelse: cannot quote builtin
address@hidden
-cmp(`q(`1', `2', defn(`defn'))', ```1',`2',<defn>'')-fixme
address@hidden:stdin:10: Warning: ifelse: cannot quote builtin
address@hidden
-cmp(`q(`1', `2', defn(`defn'))', ```1',`2',`''')-fixme
address@hidden:stdin:11: Warning: ifelse: cannot quote builtin
address@hidden
+cmp(`q(`1', `2', defn(`defn'))', `q(`1', `2', defn(`d'))')
address@hidden
+cmp(`q(`1', `2', defn(`defn'))', `q(`1', `2', `<defn>')')
address@hidden
+cmp(`q(`1', `2', defn(`defn'))', ```1',`2',<defn>'')
address@hidden
+cmp(`q(`1', `2', defn(`defn'))', ```1',`2',`''')
address@hidden
 define(`cat', `$1`'ifelse(`$#', `1', `', `$0(shift($@@))')')
 @result{}
-cat(`define(`foo',', defn(`divnum'), `)foo')-fixme
address@hidden:stdin:13: Warning: ifelse: cannot quote builtin
address@hidden
-cat(e(`define(`bar',', defn(`divnum'), `)bar'))-fixme
address@hidden:stdin:14: Warning: ifelse: cannot quote builtin
address@hidden
-m4wrap(`u('q(`cat(`define(`baz','', defn(`divnum'), ``)baz')')`)-fixme
+cat(`define(`foo',', defn(`divnum'), `)foo')
address@hidden
+cat(e(`define(`bar',', defn(`divnum'), `)bar'))
address@hidden
+m4wrap(`u('q(`cat(`define(`baz','', defn(`divnum'), ``)baz')')`)
 ')
address@hidden:stdin:15: Warning: m4wrap: cannot quote builtin
 @result{}
 ^D
address@hidden
address@hidden
 @end example
 @end ignore
 
@@ -3811,7 +3803,7 @@ echo(`1', `long string')
 @error{}m4trace: -1- echo(`1', `long s...') -> ``1',`l...'
 @result{}1,long string
 indir(`echo', defn(`changequote'))
address@hidden: -2- defn(`change...')
address@hidden: -2- defn(`change...') -> `<changequote>'
 @error{}m4trace: -1- indir(`echo', <changequote>) -> ``<changequote>''
 @result{}
 @end example
@@ -4723,6 +4715,24 @@ m4wrap(`m4wrap(`)')len(abc')
 @error{}m4:stdin:1: len: end of file in argument list
 @end example
 
+As of M4 1.6, @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
 
diff --git a/src/builtin.c b/src/builtin.c
index e9856a8..07d2ce0 100644
--- a/src/builtin.c
+++ b/src/builtin.c
@@ -123,7 +123,7 @@ builtin_tab[] =
   { "indir",           true,   true,   true,   m4_indir },
   { "len",             false,  false,  true,   m4_len },
   { "m4exit",          false,  false,  false,  m4_m4exit },
-  { "m4wrap",          false,  false,  true,   m4_m4wrap },
+  { "m4wrap",          false,  true,   true,   m4_m4wrap },
   { "maketemp",                false,  false,  true,   m4_maketemp },
   { "mkstemp",         false,  false,  true,   m4_mkstemp },
   { "patsubst",                true,   false,  true,   m4_patsubst },
@@ -204,19 +204,25 @@ find_builtin_by_name (const char *name)
 
 /*------------------------------------------------------------------.
 | Print a representation of FUNC to OBS.  If FLATTEN, output QUOTES |
-| around an empty string instead.                                   |
+| around an empty string instead; else if CHAIN, append the builtin |
+| to the chain; otherwise print the name of FUNC.                   |
 `------------------------------------------------------------------*/
 void
 func_print (struct obstack *obs, const builtin *func, bool flatten,
-           const string_pair *quotes)
+           token_chain **chain, const string_pair *quotes)
 {
   assert (func);
-  if (flatten && quotes)
+  if (flatten)
     {
-      obstack_grow (obs, quotes->str1, quotes->len1);
-      obstack_grow (obs, quotes->str2, quotes->len2);
+      if (quotes)
+       {
+         obstack_grow (obs, quotes->str1, quotes->len1);
+         obstack_grow (obs, quotes->str2, quotes->len2);
+       }
     }
-  else if (!flatten)
+  else if (chain)
+    append_macro (obs, func->func, NULL, chain);
+  else
     {
       obstack_1grow (obs, '<');
       obstack_grow (obs, func->name, strlen (func->name));
@@ -1022,10 +1028,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:
@@ -1548,7 +1552,7 @@ m4_errprint (struct obstack *obs, int argc, 
macro_arguments *argv)
 
   if (bad_argc (ARG (0), argc, 1, -1))
     return;
-  arg_print (obs, argv, 1, NULL, true, " ", NULL, false);
+  arg_print (obs, argv, 1, NULL, true, NULL, " ", NULL, false);
   debug_flush_files ();
   len = obstack_object_size (obs);
   /* The close_stdin module makes it safe to skip checking the return
diff --git a/src/debug.c b/src/debug.c
index 46e1306..fde6c49 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -368,7 +368,7 @@ trace_pre (const char *name, int id, macro_arguments *argv)
       trace_format ("(");
       arg_print (&trace, argv, 1,
                 (debug_level & DEBUG_TRACE_QUOTE) ? &curr_quote : NULL,
-                false, ", ", &len, true);
+                false, NULL, ", ", &len, true);
       trace_format (")");
     }
 
diff --git a/src/input.c b/src/input.c
index 86db704..3a913b7 100644
--- a/src/input.c
+++ b/src/input.c
@@ -72,10 +72,10 @@
 /* Type of an input block.  */
 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.  */
+  INPUT_EOF    /* Placeholder at bottom of input stack.  */
 };
 
 typedef enum input_type input_type;
@@ -103,7 +103,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.  */
@@ -136,15 +135,19 @@ static struct obstack *current_input;
 /* Bottom of token_stack, for obstack_free.  */
 static void *token_bottom;
 
-/* Pointer to top of current_input.  */
+/* Pointer to top of current_input, never NULL.  */
 static input_block *isp;
 
-/* Pointer to top of wrapup_stack.  */
+/* Pointer to top of wrapup_stack, never NULL.  */
 static input_block *wsp;
 
-/* Aux. for handling split push_string ().  */
+/* Auxiliary for handling split push_string (), NULL if not pushing
+   text for rescanning.  */
 static input_block *next;
 
+/* Marker at the end of the input stack.  */
+static input_block input_eof = { NULL, INPUT_EOF, "", 0 };
+
 /* Flag for next_char () to increment current_line.  */
 static bool start_of_input_line;
 
@@ -265,33 +268,50 @@ push_file (FILE *fp, const char *title, bool close)
   isp = i;
 }
 
-/*-----------------------------------------------------------------.
-| push_macro () pushes the builtin macro FUNC on the input stack.  |
-| If next is non-NULL, this push invalidates a call to             |
-| push_string_init (), whose storage is consequently released.     |
-`-----------------------------------------------------------------*/
-
+/*------------------------------------------------------------------.
+| Given an obstack OBS, capture any unfinished text as a link, then |
+| append the builtin FUNC as the next link in the chain that starts |
+| at *START and ends at *END.  START may be NULL if *END is         |
+| non-NULL.                                                         |
+`------------------------------------------------------------------*/
 void
-push_macro (builtin_func *func)
+append_macro (struct obstack *obs, builtin_func *func, token_chain **start,
+             token_chain **end)
 {
-  input_block *i;
-
-  if (next != NULL)
-    {
-      obstack_free (current_input, next);
-      next = NULL;
-    }
+  token_chain *chain;
 
   assert (func);
-  i = (input_block *) obstack_alloc (current_input, sizeof *i);
-  i->type = INPUT_MACRO;
-  i->file = current_file;
-  i->line = current_line;
-  input_change = true;
+  make_text_link (obs, start, end);
+  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 the builtin FUNC 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);
 }
 
 /*--------------------------------------------------------------.
@@ -304,7 +324,7 @@ push_string_init (void)
 {
   /* Free any memory occupied by completely parsed strings.  */
   assert (next == NULL);
-  while (isp && pop_input (false));
+  while (pop_input (false));
 
   /* Reserve the next location on the obstack.  */
   next = (input_block *) obstack_alloc (current_input, sizeof *next);
@@ -361,7 +381,18 @@ push_token (token_data *token, int level, bool inuse)
          return false;
        }
     }
-  else if (TOKEN_DATA_TYPE (token) != TOKEN_FUNC)
+  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
         additional references for long text segments is more
@@ -407,23 +438,15 @@ push_token (token_data *token, int level, bool inuse)
       adjust_refcount (level, true);
       inuse = true;
     }
-  else if (TOKEN_DATA_TYPE (token) == TOKEN_FUNC)
-    {
-      chain = (token_chain *) obstack_alloc (current_input, sizeof *chain);
-      if (next->u.u_c.end)
-       next->u.u_c.end->next = chain;
-      else
-       next->u.u_c.chain = chain;
-      next->u.u_c.end = chain;
-      chain->next = NULL;
-      chain->type = CHAIN_FUNC;
-      chain->quote_age = 0;
-      chain->u.func = TOKEN_DATA_FUNC (token);
-    }
   while (src_chain)
     {
-      /* TODO support func concatenation.  */
-      assert (src_chain->type != CHAIN_FUNC);
+      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.  */
@@ -526,13 +549,13 @@ push_string_finish (void)
 `--------------------------------------------------------------*/
 
 struct obstack *
-push_wrapup_init (void)
+push_wrapup_init (token_chain ***end)
 {
   input_block *i;
   token_chain *chain;
 
   assert (obstack_object_size (wrapup_stack) == 0);
-  if (wsp)
+  if (wsp != &input_eof)
     {
       i = wsp;
       assert (i->type == INPUT_CHAIN && i->u.u_c.end
@@ -559,6 +582,7 @@ push_wrapup_init (void)
   chain->quote_age = 0;
   chain->u.u_l.file = current_file;
   chain->u.u_l.line = current_line;
+  *end = &i->u.u_c.end;
   return wrapup_stack;
 }
 
@@ -596,12 +620,6 @@ pop_input (bool cleanup)
        return false;
       break;
 
-    case INPUT_MACRO:
-      assert (!isp->u.func || !cleanup);
-      if (isp->u.func)
-       return false;
-      break;
-
     case INPUT_CHAIN:
       chain = isp->u.u_c.chain;
       assert (!chain || !cleanup);
@@ -658,6 +676,9 @@ pop_input (bool cleanup)
       output_current_line = -1;
       break;
 
+    case INPUT_EOF:
+      return false;
+
     default:
       assert (!"pop_input");
       abort ();
@@ -684,7 +705,7 @@ pop_wrapup (void)
   obstack_free (current_input, NULL);
   free (current_input);
 
-  if (wsp == NULL)
+  if (wsp == &input_eof)
     {
       /* End of the program.  Free all memory even though we are about
         to exit, since it makes leak detection easier.  */
@@ -703,7 +724,7 @@ pop_wrapup (void)
   obstack_init (wrapup_stack);
 
   isp = wsp;
-  wsp = NULL;
+  wsp = &input_eof;
   input_change = true;
 
   return true;
@@ -730,9 +751,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:
       chain = input->u.u_c.chain;
       while (chain)
@@ -746,14 +764,14 @@ input_print (struct obstack *obs, const input_block 
*input)
              break;
            case CHAIN_FUNC:
              func_print (obs, find_builtin_by_addr (chain->u.func), false,
-                         NULL);
+                         NULL, NULL);
              break;
            case CHAIN_ARGV:
              assert (!chain->u.u_a.comma);
              if (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, NULL, &maxlen, false))
+                            chain->u.u_a.flatten, NULL, NULL, &maxlen, false))
                return;
              break;
            default:
@@ -789,9 +807,7 @@ peek_input (bool allow_argv)
 
   while (1)
     {
-      if (block == NULL)
-       return CHAR_EOF;
-
+      assert (block);
       switch (block->type)
        {
        case INPUT_STRING:
@@ -809,11 +825,6 @@ peek_input (bool allow_argv)
          block->u.u_f.end = true;
          break;
 
-       case INPUT_MACRO:
-         if (block->u.func)
-           return CHAR_MACRO;
-         break;
-
        case INPUT_CHAIN:
          chain = block->u.u_c.chain;
          while (chain)
@@ -863,6 +874,9 @@ peek_input (bool allow_argv)
            }
          break;
 
+       case INPUT_EOF:
+         return CHAR_EOF;
+
        default:
          assert (!"peek_input");
          abort ();
@@ -887,7 +901,7 @@ peek_input (bool allow_argv)
 `-------------------------------------------------------------------*/
 
 #define next_char(AQ, AA)                                              \
-  (isp && isp->type == INPUT_STRING && isp->u.u_s.len && !input_change \
+  (isp->type == INPUT_STRING && isp->u.u_s.len && !input_change                
\
    ? (isp->u.u_s.len--, to_uchar (*isp->u.u_s.str++))                  \
    : next_char_1 (AQ, AA))
 
@@ -899,13 +913,7 @@ next_char_1 (bool allow_quote, bool allow_argv)
 
   while (1)
     {
-      if (isp == NULL)
-       {
-         current_file = "";
-         current_line = 0;
-         return CHAR_EOF;
-       }
-
+      assert (isp);
       if (input_change)
        {
          current_file = isp->file;
@@ -940,11 +948,6 @@ next_char_1 (bool allow_quote, bool allow_argv)
            }
          break;
 
-       case INPUT_MACRO:
-         if (isp->u.func)
-           return CHAR_MACRO;
-         break;
-
        case INPUT_CHAIN:
          chain = isp->u.u_c.chain;
          while (chain)
@@ -1013,6 +1016,9 @@ next_char_1 (bool allow_quote, bool allow_argv)
            }
          break;
 
+       case INPUT_EOF:
+         return CHAR_EOF;
+
        default:
          assert (!"next_char_1");
          abort ();
@@ -1064,28 +1070,15 @@ init_macro_token (token_data *td)
 {
   token_chain *chain;
 
-  if (isp->type == INPUT_MACRO)
+  assert (isp->type == INPUT_CHAIN);
+  chain = isp->u.u_c.chain;
+  assert (!chain->quote_age && chain->type == CHAIN_FUNC && chain->u.func);
+  if (td)
     {
-      assert (isp->u.func);
-      if (td)
-       {
-         TOKEN_DATA_TYPE (td) = TOKEN_FUNC;
-         TOKEN_DATA_FUNC (td) = isp->u.func;
-       }
-      isp->u.func = NULL;
-    }
-  else
-    {
-      assert (isp->type == INPUT_CHAIN);
-      chain = isp->u.u_c.chain;
-      assert (!chain->quote_age && chain->type == CHAIN_FUNC && chain->u.func);
-      if (td)
-       {
-         TOKEN_DATA_TYPE (td) = TOKEN_FUNC;
-         TOKEN_DATA_FUNC (td) = chain->u.func;
-       }
-      chain->u.func = NULL;
+      TOKEN_DATA_TYPE (td) = TOKEN_FUNC;
+      TOKEN_DATA_FUNC (td) = chain->u.func;
     }
+  chain->u.func = NULL;
 }
 
 /*-------------------------------------------------------------------.
@@ -1282,8 +1275,8 @@ input_init (void)
   obstack_init (&token_stack);
   token_bottom = obstack_finish (&token_stack);
 
-  isp = NULL;
-  wsp = NULL;
+  isp = &input_eof;
+  wsp = &input_eof;
   next = NULL;
 
   start_of_input_line = false;
diff --git a/src/m4.h b/src/m4.h
index 54dd9da..3e1fdbb 100644
--- a/src/m4.h
+++ b/src/m4.h
@@ -393,11 +393,13 @@ void skip_line (const char *);
 /* push back input */
 void make_text_link (struct obstack *, token_chain **, token_chain **);
 void push_file (FILE *, const char *, bool);
-void push_macro (builtin_func *);
+void append_macro (struct obstack *, builtin_func *, token_chain **,
+                  token_chain **);
+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 (token_chain ***);
 void push_wrapup_finish (void);
 bool pop_wrapup (void);
 void input_print (struct obstack *, const input_block *);
@@ -509,7 +511,8 @@ size_t arg_len (macro_arguments *, unsigned int);
 builtin_func *arg_func (macro_arguments *, unsigned int);
 struct obstack *arg_scratch (void);
 bool arg_print (struct obstack *, macro_arguments *, unsigned int,
-               const string_pair *, bool, const char *, size_t *, bool);
+               const string_pair *, bool, token_chain **, const char *,
+               size_t *, bool);
 macro_arguments *make_argv_ref (macro_arguments *, const char *, size_t,
                                bool, bool);
 void push_arg (struct obstack *, macro_arguments *, unsigned int);
@@ -569,7 +572,8 @@ const char *ntoa (int32_t, int);
 
 const builtin *find_builtin_by_addr (builtin_func *);
 const builtin *find_builtin_by_name (const char *);
-void func_print (struct obstack *, const builtin *, bool, const string_pair *);
+void func_print (struct obstack *, const builtin *, bool, token_chain **,
+                const string_pair *);
 
 /* File: path.c  --- path search for include files.  */
 
diff --git a/src/macro.c b/src/macro.c
index 6a6a90c..c435644 100644
--- a/src/macro.c
+++ b/src/macro.c
@@ -582,8 +582,7 @@ collect_arguments (symbol *sym, struct obstack *arguments,
   argv->wrapper = args.wrapper;
   argv->has_ref = args.has_ref;
   argv->has_func = args.has_func;
-  /* TODO allow funcs without crippling quote age.  */
-  if (args.quote_age != quote_age () || args.has_func)
+  if (args.quote_age != quote_age ())
     argv->quote_age = 0;
   argv->arraylen = args.arraylen;
   return argv;
@@ -910,8 +909,6 @@ arg_type (macro_arguments *argv, unsigned int index)
     type = TOKEN_TEXT;
   if (type != TOKEN_TEXT)
     assert (argv->has_func);
-  /* TODO support TOKEN_COMP meaning concatenation of builtins.  */
-  assert (type != TOKEN_COMP);
   return type;
 }
 
@@ -936,7 +933,6 @@ arg_text (macro_arguments *argv, unsigned int index)
     case TOKEN_TEXT:
       return TOKEN_DATA_TEXT (token);
     case TOKEN_COMP:
-      /* TODO - concatenate functions.  */
       chain = token->u.u_c.chain;
       obs = arg_scratch ();
       while (chain)
@@ -952,7 +948,7 @@ arg_text (macro_arguments *argv, unsigned int index)
                         quote_cache (NULL, chain->quote_age,
                                      chain->u.u_a.quotes),
                         argv->flatten || chain->u.u_a.flatten, NULL, NULL,
-                        false);
+                        NULL, false);
              break;
            default:
              assert (!"arg_text");
@@ -983,6 +979,7 @@ arg_equal (macro_arguments *argv, unsigned int indexa, 
unsigned int indexb)
   token_chain tmpb;
   token_chain *ca = &tmpa;
   token_chain *cb = &tmpb;
+  token_chain *chain;
   struct obstack *obs = arg_scratch ();
 
   /* Quick tests.  */
@@ -1041,30 +1038,34 @@ arg_equal (macro_arguments *argv, unsigned int indexa, 
unsigned int indexb)
     {
       if (ca->type == CHAIN_ARGV)
        {
-         tmpa.next = ca->next;
+         tmpa.next = NULL;
          tmpa.type = CHAIN_STR;
-         /* TODO support $@ with funcs.  */
-         assert (!ca->u.u_a.has_func || argv->flatten || ca->u.u_a.flatten);
+         tmpa.u.u_s.str = NULL;
+         tmpa.u.u_s.len = 0;
+         chain = &tmpa;
          arg_print (obs, ca->u.u_a.argv, ca->u.u_a.index,
                     quote_cache (NULL, ca->quote_age, ca->u.u_a.quotes),
-                    argv->flatten || ca->u.u_a.flatten, NULL, NULL, false);
-         tmpa.u.u_s.len = obstack_object_size (obs);
-         tmpa.u.u_s.str = (char *) obstack_finish (obs);
-         ca = &tmpa;
+                    argv->flatten || ca->u.u_a.flatten, &chain, NULL, NULL,
+                    false);
+         assert (obstack_object_size (obs) == 0 && chain != &tmpa);
+         chain->next = ca->next;
+         ca = tmpa.next;
          continue;
        }
       if (cb->type == CHAIN_ARGV)
        {
-         tmpb.next = cb->next;
+         tmpb.next = NULL;
          tmpb.type = CHAIN_STR;
-         /* TODO support $@ with funcs.  */
-         assert (!cb->u.u_a.has_func || argv->flatten || cb->u.u_a.flatten);
+         tmpb.u.u_s.str = NULL;
+         tmpb.u.u_s.len = 0;
+         chain = &tmpb;
          arg_print (obs, cb->u.u_a.argv, cb->u.u_a.index,
                     quote_cache (NULL, cb->quote_age, cb->u.u_a.quotes),
-                    argv->flatten || cb->u.u_a.flatten, NULL, NULL, false);
-         tmpb.u.u_s.len = obstack_object_size (obs);
-         tmpb.u.u_s.str = (char *) obstack_finish (obs);
-         cb = &tmpb;
+                    argv->flatten || cb->u.u_a.flatten, &chain, NULL, NULL,
+                    false);
+         assert (obstack_object_size (obs) == 0 && chain != &tmpb);
+         chain->next = cb->next;
+         cb = tmpb.next;
          continue;
        }
       if (ca->type == CHAIN_FUNC)
@@ -1223,18 +1224,21 @@ arg_scratch (void)
 
 /* Dump a representation of ARGV to the obstack OBS, starting with
    argument INDEX.  If QUOTES is non-NULL, each argument is displayed
-   with those quotes.  If FLATTEN, builtins are ignored.  Separate
-   arguments with SEP, which defaults to a comma.  If MAX_LEN is
-   non-NULL, truncate the output after *MAX_LEN bytes are output and
-   return true; otherwise, return false, and reduce *MAX_LEN by the
-   number of bytes output.  If QUOTE_EACH, the truncation length is
-   reset for each argument, quotes do not count against length, and
-   all arguments are printed; otherwise, quotes count against the
-   length and trailing arguments may be discarded.  */
+   with those quotes.  If FLATTEN, builtins are converted to empty
+   quotes; if CHAINP, *CHAINP is updated with macro tokens; otherwise,
+   builtins are represented by their name.  Separate arguments with
+   SEP, which defaults to a comma.  If MAX_LEN is non-NULL, truncate
+   the output after *MAX_LEN bytes are output and return true;
+   otherwise, return false, and reduce *MAX_LEN by the number of bytes
+   output.  If QUOTE_EACH, the truncation length is reset for each
+   argument, quotes do not count against length, and all arguments are
+   printed; otherwise, quotes count against the length and trailing
+   arguments may be discarded.  MAX_LEN and CHAINP may not both be
+   specified.  */
 bool
 arg_print (struct obstack *obs, macro_arguments *argv, unsigned int index,
-          const string_pair *quotes, bool flatten, const char *sep,
-          size_t *max_len, bool quote_each)
+          const string_pair *quotes, bool flatten, token_chain **chainp,
+          const char *sep, size_t *max_len, bool quote_each)
 {
   size_t len = max_len ? *max_len : INT_MAX;
   unsigned int i;
@@ -1245,6 +1249,8 @@ arg_print (struct obstack *obs, macro_arguments *argv, 
unsigned int index,
   size_t sep_len;
   size_t *plen = quote_each ? NULL : &len;
 
+  if (chainp)
+    assert (!max_len && *chainp);
   if (!sep)
     sep = ",";
   sep_len = strlen (sep);
@@ -1287,13 +1293,13 @@ arg_print (struct obstack *obs, macro_arguments *argv, 
unsigned int index,
                  break;
                case CHAIN_FUNC:
                  func_print (obs, find_builtin_by_addr (chain->u.func),
-                             flatten, quotes);
+                             flatten, chainp, 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,
                                              chain->u.u_a.quotes),
-                                flatten, NULL, &len, false))
+                                flatten, chainp, NULL, &len, false))
                    done = true;
                  break;
                default:
@@ -1310,7 +1316,7 @@ arg_print (struct obstack *obs, macro_arguments *argv, 
unsigned int index,
          break;
        case TOKEN_FUNC:
          func_print (obs, find_builtin_by_addr (TOKEN_DATA_FUNC (token)),
-                     flatten, quotes);
+                     flatten, chainp, quotes);
          break;
        default:
          assert (!"arg_print");
@@ -1319,6 +1325,8 @@ arg_print (struct obstack *obs, macro_arguments *argv, 
unsigned int index,
     }
   if (max_len)
     *max_len = len;
+  else if (chainp)
+    make_text_link (obs, NULL, chainp);
   return false;
 }
 
@@ -1524,11 +1532,12 @@ wrap_args (macro_arguments *argv)
   struct obstack *obs;
   token_data *token;
   token_chain *chain;
+  token_chain **end;
 
   if ((argv->argc == 2 || no_gnu_extensions) && arg_empty (argv, 1))
     return;
 
-  obs = push_wrapup_init ();
+  obs = push_wrapup_init (&end);
   for (i = 1; i < (no_gnu_extensions ? 2 : argv->argc); i++)
     {
       if (i != 1)
@@ -1540,8 +1549,8 @@ wrap_args (macro_arguments *argv)
          obstack_grow (obs, TOKEN_DATA_TEXT (token), TOKEN_DATA_LEN (token));
          break;
        case TOKEN_FUNC:
-         /* TODO allow builtins through m4wrap.  */
-         assert (false);
+         append_macro (obs, TOKEN_DATA_FUNC (token), NULL, end);
+         break;
        case TOKEN_COMP:
          chain = token->u.u_c.chain;
          while (chain)
@@ -1552,14 +1561,13 @@ wrap_args (macro_arguments *argv)
                  obstack_grow (obs, chain->u.u_s.str, chain->u.u_s.len);
                  break;
                case CHAIN_FUNC:
-                 /* TODO allow builtins through m4wrap.  */
-                 assert (false);
+                 append_macro (obs, chain->u.func, NULL, end);
                  break;
                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, NULL, NULL, false);
+                            chain->u.u_a.flatten, end, NULL, NULL, false);
                  break;
                default:
                  assert (!"wrap_args");
-- 
1.5.5


reply via email to

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