m4-patches
[Top][All Lists]
Advanced

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

argv_ref patch 19: support builtin tokens in more macros


From: Eric Blake
Subject: argv_ref patch 19: support builtin tokens in more macros
Date: Fri, 14 Mar 2008 07:09:36 -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

This is stage 19 of 18 :)  Actually, at this point, I am now (slowly)
merging the remaining patch series from the argv_ref speedup branch onto
the branch-1_4 and master branches.  There are still a couple of speedups
to go on the argv_ref branch before I release a stable version of m4 1.4.11.

This patch expands the framework created by earlier patches to support a
builtin token in the middle of a FIFO chain of expansion text.  Right now,
the only way to hit this scenario is through parameter expansion, and even
that is limited (defn with multiple arguments still does not support
builtin tokens alongside anything else, and builtin tokens encountered
inside comments or quotes are flattened).  There should be no memory
impact (the token_chain union did not increase in size), and maybe a
slight speed penalty due to more bookkeeping.  This patch brings GNU M4
behavior in line with both BSD and Solaris behavior on a couple of fronts:

First, all user macros now transparently handle builtin tokens in
parameter expansions, rather than flattening it to the empty string.
Therefore,
~ define(`foo', `define(`$1',$2)')foo(`bar', defn(`divnum'))bar
now results in 0 (bar defined to <divnum>) rather than '' (<divnum> was
flattened while expanding foo, and bar defined to the empty string).

Second, comparison of macro definitions is now transparent to how those
macros are implemented.  A user no longer needs to know whether a macro is
builtin or not.  For example,
~ ifelse(defn(`divnum'),defn(`dnl'),yes,no)
now results in 'no' (those two macros are quite different) rather than yes
(those two macros are builtins, both got flattened to '', and two empty
strings are equal).

Finally, this patch makes GNU M4 consistent on handling builtin tokens
encountered where a macro name was expected.  Here, there is no comparison
to other implementations (both Solaris and BSD forbid defining the empty
string as a macro name), but it is a lot nicer that if 'define' rejects a
macro name, other things like 'popdef' or 'traceon' should do so as well.

On the master branch, I further divided the patch into three parts.  The
first part caches builtins loaded from a module (also useful for a later
patch that will make tracing a builtin possible), the second swaps the
sense of the bit stating whether a builtin plans on supporting builtin
tokens as arguments (making the logic consistent that a macro must request
flattening, now that more macros handle builtins), and the third actually
fixing the input engine to support references to builtin tokens.

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

        Stage 19: allow builtin tokens in more macros.
        Allow builtin tokens inside symbol chains, although for now, they
        are not allowed inside comments or quotes.  Enable builtin token
        handling in more macros, if only to consistently diagnose invalid
        macro names.
        Memory impact: none.
        Speed impact: slight impact, due to more bookkeeping.
        * src/m4.h (enum token_chain_type): Add CHAIN_FUNC.
        (struct token_chain): Add func member.
        * src/input.c (push_token, pop_input, input_print, peek_input)
        (next_char_1, init_macro_token): Handle builtin tokens from
        back-references.
        (next_token): Flatten builtin tokens inside comments or quotes,
        except when a builtin is the only thing inside quotes.
        * src/macro.c (expand_argument): Strengthen assertion.
        (collect_arguments): Handle builtin tokens.
        (expand_macro): Fix harmless typo.
        (arg_token): Add parameter.
        (arg_type, arg_text, arg_equal, arg_empty, arg_len, arg_func)
        (arg_print, push_arg_quote, push_args): Update callers to either
        require flattened arguments or to handle builtins.
        * src/builtin.c (m4_defn, m4_dumpdef, m4_ifdef, m4_ifelse)
        (m4_popdef, m4_shift, m4_traceoff, m4_traceon, m4_undefine):
        Handle builtin tokens, either by recognizing invalid macro names
        or passing them through transparently.
        (define_user_macro): Make all user macros handle builtin token
        arguments transparently.
        * doc/m4.texinfo (Defn, Ifdef, Ifelse, Debuglen): Document and
        test the new behavior.
        * NEWS: Document this change.

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

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

iEYEARECAAYFAkfaeMMACgkQ84KuGfSFAYBvZgCdGNYSMn+uRyRfBIV/XoRfvE6l
3oUAn3DKnKN3VyIOcVuIm/Wvrsz95Ywo
=srT9
-----END PGP SIGNATURE-----
>From d621af1bae17e8f6dc2384b71acab9ef21ef51bd Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Thu, 13 Mar 2008 13:43:15 -0600
Subject: [PATCH] Stage 19a: sort and cache builtins loaded by a module.

* m4/m4module.h (m4_set_symbol_value_builtin): Delete.  Use
m4_builtin_find_by_* instead.
(m4_builtin_find_by_func): Change return type.
* m4/m4private.h (m4__builtin): New struct.
(m4_module): Add sorted list of loaded builtins.
(struct m4_symbol_value): Change type of builtin value.
(m4__set_symbol_value_builtin): New prototype and fast accessor.
(m4_get_symbol_value_func, m4_get_symbol_value_builtin): Adjust to
new field type.
* m4/symtab.c (m4_set_symbol_value_builtin): Rename...
(m4__set_symbol_value_builtin): ...and populate additional fields,
based on new type.
(m4_get_symbol_value_func, m4_get_symbol_value_builtin): Adjust to
new field type.
* m4/module.c (install_builtin_table): Use cached table.
(compare_builtin_CB): New helper function.
(m4__module_open): Populate table.
(module_remove): Free table.
* m4/builtin.c (compare_builtin_name_CB): New helper function.
(m4_builtin_find_by_name): Rewrite to use sorted table.
(m4_builtin_find_by_func): Change return type.
* m4/input.c (struct m4_input_block): Simplify u_b, since most
fields can be determined from builtin.
(builtin_peek, builtin_read, builtin_unget, init_builtin_token)
(m4__next_token): Alter parsing so that only init_builtin_token
consumes a builtin.
(builtin_print, m4_push_builtin): Adjust all users.
* tests/macros.at (Arity, defn, and freeze): Fix typo.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog       |   36 ++++++++++++++++++
 m4/builtin.c    |   56 ++++++++++++++-------------
 m4/input.c      |   63 +++++++++----------------------
 m4/m4module.h   |    4 +-
 m4/m4private.h  |   44 +++++++++++++++------
 m4/module.c     |  110 ++++++++++++++++++++++++++++++++++++-------------------
 m4/symtab.c     |   18 +++++---
 tests/macros.at |    2 +-
 8 files changed, 199 insertions(+), 134 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 1e1d811..9c98241 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,41 @@
 2008-03-13  Eric Blake  <address@hidden>
 
+       Stage 19a: sort and cache builtins loaded by a module.
+       Rather than repeatedly using dlsym to browse the builtin table,
+       copy off the table at module load time.  Then, the input engine
+       merely refers to the copy instead of duplicating information.
+       Memory impact: slight penalty, due to more memory per module, but
+       offset by less memory in input engine.
+       Speed impact: slight improvement, due to faster builtin lookups.
+       * m4/m4module.h (m4_set_symbol_value_builtin): Delete.  Use
+       m4_builtin_find_by_* instead.
+       (m4_builtin_find_by_func): Change return type.
+       * m4/m4private.h (m4__builtin): New struct.
+       (m4_module): Add sorted list of loaded builtins.
+       (struct m4_symbol_value): Change type of builtin value.
+       (m4__set_symbol_value_builtin): New prototype and fast accessor.
+       (m4_get_symbol_value_func, m4_get_symbol_value_builtin): Adjust to
+       new field type.
+       * m4/symtab.c (m4_set_symbol_value_builtin): Rename...
+       (m4__set_symbol_value_builtin): ...and populate additional fields,
+       based on new type.
+       (m4_get_symbol_value_func, m4_get_symbol_value_builtin): Adjust to
+       new field type.
+       * m4/module.c (install_builtin_table): Use cached table.
+       (compare_builtin_CB): New helper function.
+       (m4__module_open): Populate table.
+       (module_remove): Free table.
+       * m4/builtin.c (compare_builtin_name_CB): New helper function.
+       (m4_builtin_find_by_name): Rewrite to use sorted table.
+       (m4_builtin_find_by_func): Change return type.
+       * m4/input.c (struct m4_input_block): Simplify u_b, since most
+       fields can be determined from builtin.
+       (builtin_peek, builtin_read, builtin_unget, init_builtin_token)
+       (m4__next_token): Alter parsing so that only init_builtin_token
+       consumes a builtin.
+       (builtin_print, m4_push_builtin): Adjust all users.
+       * tests/macros.at (Arity, defn, and freeze): Fix typo.
+
        Consistently cast malloc results, for C++ compilation.
        * m4/builtin.c (m4_builtin_find_by_name): Add cast.
        * m4/hash.c (m4_hash_new, m4_get_hash_iterator_next, node_new)
diff --git a/m4/builtin.c b/m4/builtin.c
index 9003b5b..7f9d957 100644
--- a/m4/builtin.c
+++ b/m4/builtin.c
@@ -25,6 +25,15 @@
 
 #include "m4private.h"
 
+/* Comparison function, for use in bsearch, which compares NAME
+   against the name of BUILTIN.  */
+static int
+compare_builtin_name_CB (const void *name, const void *b)
+{
+  const m4__builtin *builtin = (const m4__builtin *) b;
+  return strcmp ((const char *) name, builtin->builtin.name);
+}
+
 /* Find the builtin which has NAME.  If MODULE is not NULL, then
    search only in MODULE's builtin table.  The result is a malloc'd
    symbol value, suitable for use in the symbol table or for an
@@ -33,26 +42,17 @@ m4_symbol_value *
 m4_builtin_find_by_name (m4_module *module, const char *name)
 {
   m4_module *cur = module ? module : m4__module_next (NULL);
+  m4__builtin *bp;
 
   do
     {
-      const m4_builtin *builtin =
-       (m4_builtin *) lt_dlsym (cur->handle, BUILTIN_SYMBOL);
-
-      if (builtin)
+      bp = (m4__builtin *) bsearch (name, cur->builtins, cur->builtins_len,
+                                   sizeof *bp, compare_builtin_name_CB);
+      if (bp)
        {
-         for (; builtin->name != NULL; builtin++)
-           if (!strcmp (builtin->name, name))
-             {
-               m4_symbol_value *token;
-               token = (m4_symbol_value *) xzalloc (sizeof *token);
-               m4_set_symbol_value_builtin (token, builtin);
-               VALUE_MODULE (token) = cur;
-               VALUE_FLAGS (token) = builtin->flags;
-               VALUE_MIN_ARGS (token) = builtin->min_args;
-               VALUE_MAX_ARGS (token) = builtin->max_args;
-               return token;
-             }
+         m4_symbol_value *token = (m4_symbol_value *) xzalloc (sizeof *token);
+         m4__set_symbol_value_builtin (token, bp);
+         return token;
        }
     }
   while (!module && (cur = m4__module_next (cur)));
@@ -61,23 +61,25 @@ m4_builtin_find_by_name (m4_module *module, const char 
*name)
 }
 
 /* Find the builtin which has FUNC.  If MODULE argument is supplied
-   then search only in MODULE's builtin table.  */
-const m4_builtin *
+   then search only in MODULE's builtin table.  The result is a
+   malloc'd symbol value, suitable for use in the symbol table or for
+   an argument to m4_push_builtin.  */
+m4_symbol_value *
 m4_builtin_find_by_func (m4_module *module, m4_builtin_func *func)
 {
   m4_module *cur = module ? module : m4__module_next (NULL);
+  size_t i;
 
   do
     {
-      const m4_builtin *builtin =
-       (const m4_builtin *) lt_dlsym (cur->handle, BUILTIN_SYMBOL);
-
-      if (builtin)
-       {
-         for (; builtin->name != NULL; builtin++)
-           if (builtin->func == func)
-             return builtin;
-       }
+      for (i = 0; i < cur->builtins_len; i++)
+       if (cur->builtins[i].builtin.func == func)
+         {
+           m4_symbol_value *token =
+             (m4_symbol_value *) xzalloc (sizeof *token);
+           m4__set_symbol_value_builtin (token, &cur->builtins[i]);
+           return token;
+         }
     }
   while (!module && (cur = m4__module_next (cur)));
 
diff --git a/m4/input.c b/m4/input.c
index 5ebf87f..b701228 100644
--- a/m4/input.c
+++ b/m4/input.c
@@ -180,15 +180,7 @@ struct m4_input_block
          bool_bitfield line_start : 1; /* Saved start_of_input_line state.  */
        }
       u_f;     /* See file_funcs.  */
-      struct
-       {
-         const m4_builtin *builtin;    /* Pointer to builtin's function.  */
-         m4_module *module;            /* Originating module.  */
-         bool_bitfield read : 1;       /* True iff block has been read.  */
-         int flags : 24;               /* Flags tied to the builtin. */
-         m4_hash *arg_signature;       /* Argument signature for builtin.  */
-       }
-      u_b;     /* See builtin_funcs.  */
+      const m4__builtin *builtin;      /* A builtin, see builtin_funcs.  */
       struct
        {
          m4__symbol_chain *chain;      /* Current link in chain.  */
@@ -397,42 +389,36 @@ static int
 builtin_peek (m4_input_block *me, m4 *context M4_GNUC_UNUSED,
              bool allow_argv M4_GNUC_UNUSED)
 {
-  if (me->u.u_b.read)
-    return CHAR_RETRY;
-
-  return CHAR_BUILTIN;
+  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 safe M4_GNUC_UNUSED)
 {
-  if (me->u.u_b.read)
-    return CHAR_RETRY;
-
-  me->u.u_b.read = true;
-  return CHAR_BUILTIN;
+  /* 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.u_b.read);
-  me->u.u_b.read = false;
+  assert (ch == CHAR_BUILTIN && me->u.builtin);
 }
 
 static void
 builtin_print (m4_input_block *me, m4 *context, m4_obstack *obs)
 {
-  const m4_builtin *bp = me->u.u_b.builtin;
-  const char *text = bp->name;
+  const m4__builtin *bp = me->u.builtin;
+  const char *text = bp->builtin.name;
 
+  assert (bp);
   obstack_1grow (obs, '<');
   obstack_grow (obs, text, strlen (text));
   obstack_1grow (obs, '>');
   if (m4_is_debug_bit (context, M4_DEBUG_TRACE_MODULE))
     {
-      text = m4_get_module_name (me->u.u_b.module);
+      text = m4_get_module_name (bp->module);
       obstack_1grow (obs, '{');
       obstack_grow (obs, text, strlen (text));
       obstack_1grow (obs, '}');
@@ -461,17 +447,7 @@ m4_push_builtin (m4 *context, m4_symbol_value *token)
   i->funcs = &builtin_funcs;
   i->file = m4_get_current_file (context);
   i->line = m4_get_current_line (context);
-
-  i->u.u_b.builtin     = m4_get_symbol_value_builtin (token);
-  i->u.u_b.module      = VALUE_MODULE (token);
-  i->u.u_b.arg_signature = VALUE_ARG_SIGNATURE (token);
-  i->u.u_b.flags       = VALUE_FLAGS (token);
-  /* Check for bitfield truncation.  */
-  assert (i->u.u_b.flags == VALUE_FLAGS (token)
-         && i->u.u_b.builtin->min_args == VALUE_MIN_ARGS (token)
-         && i->u.u_b.builtin->max_args == VALUE_MAX_ARGS (token));
-  i->u.u_b.read                = false;
-
+  i->u.builtin = token->u.builtin;
   i->prev = isp;
   isp = i;
   input_change = true;
@@ -1079,20 +1055,18 @@ m4_pop_wrapup (m4 *context)
   return true;
 }
 
-/* When a BUILTIN token is seen, m4__next_token () uses init_builtin_token
-   to retrieve the value of the function pointer.  */
+/* Populate TOKEN with the builtin token at the top of the input
+   stack, then consume the input.  */
 static void
 init_builtin_token (m4 *context, m4_symbol_value *token)
 {
+  int ch = next_char (context, false, true);
   m4_input_block *block = isp;
-  assert (block->funcs->read_func == builtin_read && !block->u.u_b.read);
-
-  m4_set_symbol_value_builtin (token, block->u.u_b.builtin);
-  VALUE_MODULE (token)         = block->u.u_b.module;
-  VALUE_FLAGS (token)          = block->u.u_b.flags;
-  VALUE_ARG_SIGNATURE (token)  = block->u.u_b.arg_signature;
-  VALUE_MIN_ARGS (token)       = block->u.u_b.builtin->min_args;
-  VALUE_MAX_ARGS (token)       = block->u.u_b.builtin->max_args;
+  assert (ch == CHAR_BUILTIN && block->funcs->read_func == builtin_read
+         && block->u.builtin);
+
+  m4__set_symbol_value_builtin (token, block->u.builtin);
+  block->u.builtin = NULL;
 }
 
 /* When a QUOTE token is seen, convert VALUE to a composite (if it is
@@ -1501,7 +1475,6 @@ m4__next_token (m4 *context, m4_symbol_value *token, int 
*line,
     if (ch == CHAR_BUILTIN)            /* BUILTIN TOKEN */
       {
        init_builtin_token (context, token);
-       next_char (context, false, true);
 #ifdef DEBUG_INPUT
        m4_print_token (context, "next_token", M4_TOKEN_MACDEF, token);
 #endif
diff --git a/m4/m4module.h b/m4/m4module.h
index c1f1360..05bc7af 100644
--- a/m4/m4module.h
+++ b/m4/m4module.h
@@ -295,8 +295,6 @@ extern const char * m4_get_symbol_value_placeholder 
(m4_symbol_value *);
 extern void            m4_set_symbol_value_text  (m4_symbol_value *,
                                                   const char *, size_t,
                                                   unsigned int);
-extern void            m4_set_symbol_value_builtin     (m4_symbol_value *,
-                                                        const m4_builtin *);
 extern void            m4_set_symbol_value_placeholder (m4_symbol_value *,
                                                         const char *);
 
@@ -305,7 +303,7 @@ extern void         m4_set_symbol_value_placeholder 
(m4_symbol_value *,
 /* --- BUILTIN MANAGEMENT --- */
 
 extern m4_symbol_value *m4_builtin_find_by_name (m4_module *, const char *);
-extern const m4_builtin        *m4_builtin_find_by_func (m4_module *,
+extern m4_symbol_value *m4_builtin_find_by_func (m4_module *,
                                                  m4_builtin_func *);
 
 
diff --git a/m4/m4private.h b/m4/m4private.h
index 9e10441..c6cc639 100644
--- a/m4/m4private.h
+++ b/m4/m4private.h
@@ -148,6 +148,22 @@ struct m4 {
    exported in m4module.h.  */
 #define m4__get_search_path(C)                 ((C)->search_path)
 
+
+/* --- BUILTIN MANAGEMENT --- */
+
+/* Internal representation of loaded builtins.  */
+struct m4__builtin
+{
+  /* Copied from module's BUILTIN_SYMBOL table, although builtin.flags
+     can be used for additional bits beyond what is allowed for
+     modules.  */
+  m4_builtin builtin;
+  m4_module *module;           /* Module that owns this builtin.  */
+};
+typedef struct m4__builtin m4__builtin;
+
+extern void m4__set_symbol_value_builtin (m4_symbol_value *,
+                                         const m4__builtin *);
 
 
 /* --- MODULE MANAGEMENT --- */
@@ -158,15 +174,13 @@ struct m4 {
 #define INIT_SYMBOL            "m4_init_module"
 #define FINISH_SYMBOL          "m4_finish_module"
 
+/* Representation of a loaded m4 module.  */
 struct m4_module
 {
-  lt_dlhandle  handle;         /* ltdl module information.  */
-  int          refcount;       /* count of loads not matched by unload.  */
-  /* TODO: add struct members, such as copy of builtins (so that we
-     can store additional information about builtins, and so that the
-     list isn't changed by the module behind our backs once we have
-     initialized it) or cached pointers (to reduce the number of calls
-     needed to lt_dlsym).  */
+  lt_dlhandle handle;          /* All ltdl module information.  */
+  int refcount;                        /* Count of loads not matched by 
unload.  */
+  m4__builtin *builtins;       /* Sorted array of builtins.  */
+  size_t builtins_len;         /* Number of builtins.  */
 };
 
 extern void        m4__module_init (m4 *context);
@@ -248,11 +262,11 @@ struct m4_symbol_value
     {
       size_t           len;    /* Length of string.  */
       const char *     text;   /* String contents.  */
-      /* Quote age when this string was built, or zeroto force a
+      /* Quote age when this string was built, or zero to force a
         rescan of the string.  Ignored for 0 len.  */
       unsigned int     quote_age;
     } u_t;                     /* Valid when type is TEXT, PLACEHOLDER.  */
-    const m4_builtin * builtin;/* Valid when type is FUNC.  */
+    const m4__builtin *        builtin;/* Valid when type is FUNC.  */
     struct
     {
       m4__symbol_chain *chain;         /* First link of the chain.  */
@@ -348,8 +362,8 @@ extern void m4__push_arg_quote      (m4 *, m4_obstack *, 
m4_macro_args *,
 #  define m4_get_symbol_value_text(V)  ((V)->u.u_t.text)
 #  define m4_get_symbol_value_len(V)   ((V)->u.u_t.len)
 #  define m4_get_symbol_value_quote_age(V)     ((V)->u.u_t.quote_age)
-#  define m4_get_symbol_value_func(V)  ((V)->u.builtin->func)
-#  define m4_get_symbol_value_builtin(V) ((V)->u.builtin)
+#  define m4_get_symbol_value_func(V)  ((V)->u.builtin->builtin.func)
+#  define m4_get_symbol_value_builtin(V) (&(V)->u.builtin->builtin)
 #  define m4_get_symbol_value_placeholder(V)                           \
                                        ((V)->u.u_t.text)
 #  define m4_symbol_value_groks_macro(V) (BIT_TEST ((V)->flags,                
\
@@ -358,10 +372,14 @@ extern void       m4__push_arg_quote      (m4 *, 
m4_obstack *, m4_macro_args *,
 #  define m4_set_symbol_value_text(V, T, L, A)                         \
   ((V)->type = M4_SYMBOL_TEXT, (V)->u.u_t.text = (T),                  \
    (V)->u.u_t.len = (L), (V)->u.u_t.quote_age = (A))
-#  define m4_set_symbol_value_builtin(V, B)                            \
-  ((V)->type = M4_SYMBOL_FUNC, (V)->u.builtin = (B))
 #  define m4_set_symbol_value_placeholder(V, T)                                
\
   ((V)->type = M4_SYMBOL_PLACEHOLDER, (V)->u.u_t.text = (T))
+#  define m4__set_symbol_value_builtin(V, B)                           \
+  ((V)->type = M4_SYMBOL_FUNC, (V)->u.builtin = (B),                   \
+   VALUE_MODULE (V) = (B)->module,                                     \
+   VALUE_FLAGS (V) = (B)->builtin.flags,                               \
+   VALUE_MIN_ARGS (V) = (B)->builtin.min_args,                         \
+   VALUE_MAX_ARGS (V) = (B)->builtin.max_args)
 #endif
 
 
diff --git a/m4/module.c b/m4/module.c
index 7ef6ab3..4b15700 100644
--- a/m4/module.c
+++ b/m4/module.c
@@ -1,6 +1,6 @@
 /* GNU m4 -- A simple macro processor
-   Copyright (C) 1989, 1990, 1991, 1992, 1993, 1994, 1998, 1999, 2002, 2003,
-   2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
+   Copyright (C) 1989, 1990, 1991, 1992, 1993, 1994, 1998, 1999, 2002,
+   2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
 
    This file is part of GNU M4.
 
@@ -135,47 +135,28 @@ m4_module_import (m4 *context, const char *module_name,
 static void
 install_builtin_table (m4 *context, m4_module *module)
 {
-  const m4_builtin *bp;
+  size_t i;
 
   assert (context);
   assert (module);
-
-  bp = (m4_builtin *) lt_dlsym (module->handle, BUILTIN_SYMBOL);
-  if (bp)
+  for (i = 0; i < module->builtins_len; i++)
     {
-      for (; bp->name != NULL; bp++)
-       {
-         m4_symbol_value *value = m4_symbol_value_create ();
-         const char *     name;
+      m4_symbol_value *value = m4_symbol_value_create ();
+      const char *name = module->builtins[i].builtin.name;
 
-         /* Sanity check that builtins meet the required interface.  */
-         assert (bp->min_args <= bp->max_args);
-         assert (bp->min_args > 0
-                 || (bp->flags & (M4_BUILTIN_BLIND
-                                  | M4_BUILTIN_SIDE_EFFECT)) == 0);
-         assert ((bp->flags & ~M4_BUILTIN_FLAGS_MASK) == 0);
-
-         m4_set_symbol_value_builtin (value, bp);
-         VALUE_MODULE   (value)        = module;
-         VALUE_FLAGS    (value)        = bp->flags;
-         VALUE_MIN_ARGS (value)        = bp->min_args;
-         VALUE_MAX_ARGS (value)        = bp->max_args;
-
-         if (m4_get_prefix_builtins_opt (context))
-           name = xasprintf ("m4_%s", bp->name);
-         else
-           name = bp->name;
-
-         m4_symbol_pushdef (M4SYMTAB, name, value);
-
-         if (m4_get_prefix_builtins_opt (context))
-           free ((char *) name);
-       }
+      m4__set_symbol_value_builtin (value, &module->builtins[i]);
+      if (m4_get_prefix_builtins_opt (context))
+       name = xasprintf ("m4_%s", name);
 
-      m4_debug_message (context, M4_DEBUG_TRACE_MODULE,
-                       _("module %s: builtins loaded"),
-                       m4_get_module_name (module));
+      m4_symbol_pushdef (M4SYMTAB, name, value);
+
+      if (m4_get_prefix_builtins_opt (context))
+       free ((char *) name);
     }
+  if (i)
+    m4_debug_message (context, M4_DEBUG_TRACE_MODULE,
+                     _("module %s: builtins loaded"),
+                     m4_get_module_name (module));
 }
 
 static void
@@ -199,7 +180,7 @@ install_macro_table (m4 *context, m4_module *module)
          assert (mp->min_args <= mp->max_args);
 
          m4_set_symbol_value_text (value, xmemdup (mp->value, len + 1),
-                                    len, 0);
+                                   len, 0);
          VALUE_MODULE (value) = module;
          VALUE_MIN_ARGS (value) = mp->min_args;
          VALUE_MAX_ARGS (value) = mp->max_args;
@@ -390,6 +371,19 @@ m4__module_init (m4 *context)
 }
 
 
+/* Compare two builtins A and B for sorting, as in qsort.  */
+static int
+compare_builtin_CB (const void *a, const void *b)
+{
+  const m4__builtin *builtin_a = (const m4__builtin *) a;
+  const m4__builtin *builtin_b = (const m4__builtin *) b;
+  int result = strcmp (builtin_a->builtin.name, builtin_b->builtin.name);
+  /* A builtin module should never provide two builtins with the same
+     name.  */
+  assert (result || a == b);
+  return result;
+}
+
 /* Load a module.  NAME can be a absolute file name or, if relative,
    it is searched for in the module path.  The module is unloaded in
    case of error.  */
@@ -429,10 +423,44 @@ m4__module_open (m4 *context, const char *name, 
m4_obstack *obs)
        {
          void *old;
          const char *err;
+         const m4_builtin *bp;
 
          module = (m4_module *) xzalloc (sizeof *module);
          module->handle = handle;
 
+         /* TODO - change module interface to return function pointer
+            that supplies both table and length of table, rather than
+            returning data pointer that must have a sentinel
+            entry?  */
+         bp = (m4_builtin *) lt_dlsym (module->handle, BUILTIN_SYMBOL);
+         if (bp)
+           {
+             const m4_builtin *tmp;
+             m4__builtin *builtin;
+             for (tmp = bp; tmp->name; tmp++)
+               module->builtins_len++;
+             module->builtins =
+               (m4__builtin *) xnmalloc (module->builtins_len,
+                                         sizeof *module->builtins);
+             for (builtin = module->builtins; bp->name != NULL;
+                  bp++, builtin++)
+               {
+                 /* Sanity check that builtins meet the required
+                    interface.  */
+                 assert (bp->min_args <= bp->max_args);
+                 assert (bp->min_args > 0
+                         || (bp->flags & (M4_BUILTIN_BLIND
+                                          | M4_BUILTIN_SIDE_EFFECT)) == 0);
+                 assert ((bp->flags & ~M4_BUILTIN_FLAGS_MASK) == 0);
+
+                 memcpy (&builtin->builtin, bp, sizeof *bp);
+                 builtin->builtin.name = xstrdup (bp->name);
+                 builtin->module = module;
+               }
+           }
+         qsort (module->builtins, module->builtins_len,
+                sizeof *module->builtins, compare_builtin_CB);
+
          /* clear out any stale errors, since we have to use
             lt_dlerror to distinguish between success and
             failure.  */
@@ -615,7 +643,13 @@ module_remove (m4 *context, m4_module *module, m4_obstack 
*obs)
     m4_error (context, EXIT_FAILURE, 0, NULL,
              _("cannot close module `%s': %s"), name, module_dlerror ());
   if (last_reference)
-    free (module);
+    {
+      size_t i;
+      for (i = 0; i < module->builtins_len; i++)
+       free ((char *) module->builtins[i].builtin.name);
+      free (module->builtins);
+      free (module);
+    }
 
   DELETE (name);
 
diff --git a/m4/symtab.c b/m4/symtab.c
index f8b84bf..8b68132 100644
--- a/m4/symtab.c
+++ b/m4/symtab.c
@@ -788,7 +788,7 @@ m4_builtin_func *
 m4_get_symbol_value_func (m4_symbol_value *value)
 {
   assert (value && value->type == M4_SYMBOL_FUNC);
-  return value->u.builtin->func;
+  return value->u.builtin->builtin.func;
 }
 
 #undef m4_get_symbol_value_builtin
@@ -796,7 +796,7 @@ const m4_builtin *
 m4_get_symbol_value_builtin (m4_symbol_value *value)
 {
   assert (value && value->type == M4_SYMBOL_FUNC);
-  return value->u.builtin;
+  return &value->u.builtin->builtin;
 }
 
 #undef m4_get_symbol_value_placeholder
@@ -823,15 +823,19 @@ m4_set_symbol_value_text (m4_symbol_value *value, const 
char *text, size_t len,
   value->u.u_t.quote_age = quote_age;
 }
 
-#undef m4_set_symbol_value_builtin
+#undef m4__set_symbol_value_builtin
 void
-m4_set_symbol_value_builtin (m4_symbol_value *value, const m4_builtin *builtin)
+m4__set_symbol_value_builtin (m4_symbol_value *value,
+                             const m4__builtin *builtin)
 {
-  assert (value);
-  assert (builtin);
+  assert (value && builtin);
 
-  value->type   = M4_SYMBOL_FUNC;
+  value->type = M4_SYMBOL_FUNC;
   value->u.builtin = builtin;
+  VALUE_MODULE (value) = builtin->module;
+  VALUE_FLAGS (value) = builtin->builtin.flags;
+  VALUE_MIN_ARGS (value) = builtin->builtin.min_args;
+  VALUE_MAX_ARGS (value) = builtin->builtin.max_args;
 }
 
 #undef m4_set_symbol_value_placeholder
diff --git a/tests/macros.at b/tests/macros.at
index d0dd7e1..c4db9e6 100644
--- a/tests/macros.at
+++ b/tests/macros.at
@@ -82,7 +82,7 @@ AT_DATA([[input.m4]],
 [[defun
 defun(`foo')
 defun(`foo', `bar')
-defun(`foo', `bar', baz')
+defun(`foo', `bar', `baz')
 ]])
 
 AT_DATA([[expout]],
-- 
1.5.4


>From 2f49d755e50a574e5a772893f3f5d683495084bc Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Thu, 13 Mar 2008 13:48:49 -0600
Subject: [PATCH] Stage 19b: invert sense of bit for handling builtin tokens.

* m4/m4module.h (M4BUILTIN_ENTRY): New convenience macro.
(M4_BUILTIN_GROKS_MACRO): Rename...
(M4_BUILTIN_FLATTEN_ARGS): ...and invert sense.
(m4_symbol_value_groks_macro, m4_symbol_groks_macro): Likewise...
(m4_symbol_value_flatten_args, m4_symbol_flatten_args): ...to
this.
* m4/m4private.h (VALUE_MACRO_ARGS_BIT)
(m4_symbol_value_groks_macro): Likewise...
(VALUE_FLATTEN_ARGS_BIT, m4_symbol_value_flatten_args): ...to
this.
* m4/symtab.c (m4_symbol_value_groks_macro): Likewise...
(m4_symbol_value_flatten_args): ...to this.
* m4/macro.c (collect_arguments): Accomodate changed sense.
* m4/module.c (m4__module_open): Require arguments if flattening
is requested.
* m4/input.c (m4_push_string_finish): For now, flatten all
builtins pushed as back-references.
* modules/gnu.c (m4_builtin_table, builtin): Adjust all clients.
* modules/import.c (m4_builtin_table): Likewise.
* modules/load.c (m4_builtin_table): Likewise.
* modules/modtest.c (m4_builtin_table): Likewise.
* modules/mpeval.c (m4_builtin_table): Likewise.
* modules/perl.c (m4_builtin_table): Likewise.
* modules/shadow.c (m4_builtin_table): Likewise.
* modules/stdlib.c (m4_builtin_table): Likewise.
* modules/time.c (m4_builtin_table): Likewise.
* modules/m4.c (m4_builtin_table): Likewise.
(mkstemp): Undo #undef hack from 2006-10-23, now that macro names
are stringized without preprocessor expansion.
* doc/m4.texinfo (Defn): Update comments to match reality.
(Debuglen): Update test now that user macros pass builtin tokens.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog         |   37 +++++++++++++++++++++++++
 doc/m4.texinfo    |   24 ++++++++++------
 m4/input.c        |    4 +++
 m4/m4module.h     |   79 +++++++++++++++++++++++++++++++++++++++--------------
 m4/m4private.h    |    6 ++--
 m4/macro.c        |    7 +---
 m4/module.c       |    2 +
 m4/symtab.c       |    6 ++--
 modules/gnu.c     |   16 ++++-------
 modules/import.c  |   12 +++-----
 modules/load.c    |   11 +++-----
 modules/m4.c      |   13 ++-------
 modules/modtest.c |   10 ++-----
 modules/mpeval.c  |    9 ++----
 modules/perl.c    |    8 +----
 modules/shadow.c  |   10 ++-----
 modules/stdlib.c  |   10 ++-----
 modules/time.c    |   13 +++-----
 18 files changed, 160 insertions(+), 117 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 9c98241..147ea10 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,42 @@
 2008-03-13  Eric Blake  <address@hidden>
 
+       Stage 19b: invert sense of bit for handling builtin tokens.
+       Pass builtin tokens by default, rather than as the exception, so
+       that the logic can consistently refer to flattening arguments.
+       Memory impact: none.
+       Speed impact: none.
+       * m4/m4module.h (M4BUILTIN_ENTRY): New convenience macro.
+       (M4_BUILTIN_GROKS_MACRO): Rename...
+       (M4_BUILTIN_FLATTEN_ARGS): ...and invert sense.
+       (m4_symbol_value_groks_macro, m4_symbol_groks_macro): Likewise...
+       (m4_symbol_value_flatten_args, m4_symbol_flatten_args): ...to
+       this.
+       * m4/m4private.h (VALUE_MACRO_ARGS_BIT)
+       (m4_symbol_value_groks_macro): Likewise...
+       (VALUE_FLATTEN_ARGS_BIT, m4_symbol_value_flatten_args): ...to
+       this.
+       * m4/symtab.c (m4_symbol_value_groks_macro): Likewise...
+       (m4_symbol_value_flatten_args): ...to this.
+       * m4/macro.c (collect_arguments): Accomodate changed sense.
+       * m4/module.c (m4__module_open): Require arguments if flattening
+       is requested.
+       * m4/input.c (m4_push_string_finish): For now, flatten all
+       builtins pushed as back-references.
+       * modules/gnu.c (m4_builtin_table, builtin): Adjust all clients.
+       * modules/import.c (m4_builtin_table): Likewise.
+       * modules/load.c (m4_builtin_table): Likewise.
+       * modules/modtest.c (m4_builtin_table): Likewise.
+       * modules/mpeval.c (m4_builtin_table): Likewise.
+       * modules/perl.c (m4_builtin_table): Likewise.
+       * modules/shadow.c (m4_builtin_table): Likewise.
+       * modules/stdlib.c (m4_builtin_table): Likewise.
+       * modules/time.c (m4_builtin_table): Likewise.
+       * modules/m4.c (m4_builtin_table): Likewise.
+       (mkstemp): Undo #undef hack from 2006-10-23, now that macro names
+       are stringized without preprocessor expansion.
+       * doc/m4.texinfo (Defn): Update comments to match reality.
+       (Debuglen): Update test now that user macros pass builtin tokens.
+
        Stage 19a: sort and cache builtins loaded by a module.
        Rather than repeatedly using dlsym to browse the builtin table,
        copy off the table at module load time.  Then, the input engine
diff --git a/doc/m4.texinfo b/doc/m4.texinfo
index 909f8e3..018c9c8 100644
--- a/doc/m4.texinfo
+++ b/doc/m4.texinfo
@@ -2176,10 +2176,13 @@ empty string in most other contexts.
 @comment FIXME - Other implementations, such as Solaris, can pass a
 @comment builtin token around to other macros, flattening it only on output:
 @comment  define(foo, a`'defn(`divnum')b)
address@hidden  len(foo) => 3
address@hidden  index(foo, defn(`divnum') => 1
address@hidden  foo => ab
address@hidden It may be worth making some changes to support this behavior.
address@hidden  defn(`foo') => ab
address@hidden  dumpdef(`foo') => foo: a<divnum>b
address@hidden  len(defn(`foo')) => 3
address@hidden  index(defn(`foo'), defn(`divnum')) => 1
address@hidden  foo => a0b
address@hidden It may be worth making some changes to support this behavior,
address@hidden or something similar to it.
 
 The macro @code{defn} is recognized only with parameters.
 @end deffn
@@ -2279,6 +2282,7 @@ of expected contexts can sometimes trigger warnings.  But 
most of the
 time, such tokens are silently converted to the empty string.
 
 @example
+$ @kbd{m4 -d}
 defn(`defn')
 @result{}
 define(defn(`divnum'), `cannot redefine a builtin token')
@@ -2286,6 +2290,8 @@ define(defn(`divnum'), `cannot redefine a builtin token')
 @result{}
 divnum
 @result{}0
+len(defn(`divnum'))
address@hidden
 define(`echo', `$@@')
 @result{}
 define(`mydivnum', echo(defn(`divnum')))
@@ -2294,10 +2300,10 @@ mydivnum
 @result{}
 @end example
 
-Since @code{defn} can take more than one argument, it can be used to
-concatenate multiple macros into one.
address@hidden FIXME - we don't yet handle mixing text and builtins.  This
address@hidden example passes under Solaris (minus the warning).
+A warning is issued if @var{name} is undefined.  Also, at present,
+concatenating a builtin token with anything else is not supported as a
+macro definition, and a warning is issued.
address@hidden FIXME - handle defining macros with mixed text and builtins.
 
 @comment xfail
 @example
@@ -3877,7 +3883,7 @@ echo(`1', `long string')
 @result{}1,long string
 echo(defn(`changequote'))
 @error{}m4trace: -2- defn(`change...') -> <changequote>
address@hidden: -1- echo(`') -> ``''
address@hidden: -1- echo(<changequote>) -> ``''
 @result{}
 debuglen
 @result{}debuglen
diff --git a/m4/input.c b/m4/input.c
index b701228..c337ca8 100644
--- a/m4/input.c
+++ b/m4/input.c
@@ -554,6 +554,10 @@ m4__push_symbol (m4 *context, m4_symbol_value *value, 
size_t level, bool inuse)
          return false;
        }
     }
+  else if (m4_is_symbol_value_func (value))
+    {
+      /* TODO - use the builtin, rather than flattening it.  */
+    }
   else
     {
       /* For composite values, if argv is already in use, creating
diff --git a/m4/m4module.h b/m4/m4module.h
index 05bc7af..0941d2b 100644
--- a/m4/m4module.h
+++ b/m4/m4module.h
@@ -46,10 +46,11 @@ typedef void   m4_builtin_func  (m4 *, m4_obstack *, 
size_t, m4_macro_args *);
 
 /* The value of m4_builtin flags is built from these:  */
 enum {
-  /* Set if macro can handle non-text tokens, such as builtin macro
-     tokens; if clear, non-text tokens are flattened to the empty
-     string before invoking the builtin.  */
-  M4_BUILTIN_GROKS_MACRO       = (1 << 0),
+  /* Set if macro flattens non-text tokens, such as builtin macro
+     tokens, to the empty string prior to invoking the builtin; if
+     clear, non-text tokens must be transparently handled by the
+     builtin.  May only be set if max_args is nonzero.  */
+  M4_BUILTIN_FLATTEN_ARGS      = (1 << 0),
   /* Set if macro should only be recognized with arguments; may only
      be set if min_args is nonzero.  */
   M4_BUILTIN_BLIND             = (1 << 1),
@@ -89,29 +90,65 @@ struct m4_string_pair
   size_t len2;         /* Second length.  */
 };
 
+/* Declare a prototype for the function "builtin_<NAME>".  Note that
+   the function name includes any macro expansion of NAME.  */
 #define M4BUILTIN(name)                                                        
\
   static void CONC (builtin_, name)                                    \
-   (m4 *context, m4_obstack *obs, size_t argc, m4_macro_args *argv);
+    (m4 *, m4_obstack *, size_t, m4_macro_args *);
 
+/* Begin the implementation of the function "builtin_<NAME>",
+   declaring parameter names that can be used by other helper macros
+   in this file.  Note that the function name includes any macro
+   expansion of NAME.  */
 #define M4BUILTIN_HANDLER(name)                                                
\
   static void CONC (builtin_, name)                                    \
-   (m4 *context, m4_obstack *obs, size_t argc, m4_macro_args *argv)
+    (m4 *context, m4_obstack *obs, size_t argc, m4_macro_args *argv)
 
+/* Declare a prototype, then begin the implementation of the function
+   "<NAME>_LTX_m4_init_module", which will automatically be registered
+   as the initialization function for module NAME.  Note that NAME is
+   intentionally used literally, rather than subjected to macro
+   expansion.  */
 #define M4INIT_HANDLER(name)                                           \
-  void CONC (name, CONC (_LTX_, m4_init_module))                       \
-       (m4 *context, m4_module *module, m4_obstack *obs);              \
-  void CONC (name, CONC (_LTX_, m4_init_module))                       \
-       (m4 *context, m4_module *module, m4_obstack *obs)
-
+  void name ## _LTX_m4_init_module                                     \
+    (m4 *, m4_module *, m4_obstack *);                                 \
+  void name ## _LTX_m4_init_module                                     \
+    (m4 *context, m4_module *module, m4_obstack *obs)
+
+/* Declare a prototype, then begin the implementation of the function
+   "<NAME>_LTX_m4_init_module", which will automatically be registered
+   as the cleanup function for module NAME.  Note that NAME is
+   intentionally used literally, rather than subjected to macro
+   expansion.  */
 #define M4FINISH_HANDLER(name)                                         \
-  void CONC (name, CONC (_LTX_, m4_finish_module))                     \
-       (m4 *context, m4_module *module, m4_obstack *obs);              \
-  void CONC (name, CONC (_LTX_, m4_finish_module))                     \
-       (m4 *context, m4_module *module, m4_obstack *obs)
-
+  void name ## _LTX_m4_finish_module                                   \
+    (m4 *, m4_module *, m4_obstack *);                                 \
+  void name ## _LTX_m4_finish_module                                   \
+    (m4 *context, m4_module *module, m4_obstack *obs)
+
+/* Declare a variable S of type "<S>_func" to be a pointer to the
+   function named S imported from the module M, or NULL if the import
+   fails.  Note that M and S are intentionally used literally rather
+   than subjected to macro expansion, in all but the variable name.  */
 #define M4_MODULE_IMPORT(M, S)                                         \
-  CONC (S, _func) *S = (CONC (S, _func) *)                             \
-       m4_module_import (context, STR (M), STR (S), obs)
+  S ## _func *S = (S ## _func *) m4_module_import (context, #M, #S, obs)
+
+/* Build an entry in a builtin table, for the builtin N implemented by
+   the function "builtin_<N>" with name NAME.  Build the flags from
+   the appropriate combination of M4_BUILTIN_FLAG_* based on M if the
+   builtin transparently supports macro tokens, B if it is blind, and
+   S if it has side effects.  Specify that the builtin takes MIN and
+   MAX arguments.  Note that N is subject to macro expansion, and that
+   NAME is generally used as #N to avoid clashes with builtins named
+   after a standard function that is defined as a macro.  */
+#define M4BUILTIN_ENTRY(N, NAME, M, B, S, MIN, MAX)    \
+  {                                                    \
+    CONC (builtin_, N), NAME,                          \
+    (((!(M) && (MAX)) ? M4_BUILTIN_FLATTEN_ARGS : 0)   \
+     | (((B) && (MIN)) ? M4_BUILTIN_BLIND : 0)         \
+     | (((S) && (MIN)) ? M4_BUILTIN_SIDE_EFFECT : 0)), \
+    MIN, MAX                                           \
+  },
 
 /* Grab the text contents of argument I, or abort if the argument is
    not text.  Assumes that `m4 *context' and `m4_macro_args *argv' are
@@ -252,7 +289,7 @@ extern bool m4_symbol_value_print   (m4 *, m4_symbol_value 
*, m4_obstack *,
 extern void    m4_symbol_print         (m4 *, m4_symbol *, m4_obstack *,
                                         const m4_string_pair *, bool, size_t,
                                         bool);
-extern bool    m4_symbol_value_groks_macro     (m4_symbol_value *);
+extern bool    m4_symbol_value_flatten_args (m4_symbol_value *);
 
 #define m4_is_symbol_void(symbol)                                      \
        (m4_is_symbol_value_void (m4_get_symbol_value (symbol)))
@@ -272,8 +309,8 @@ extern bool m4_symbol_value_groks_macro     
(m4_symbol_value *);
        (m4_get_symbol_value_builtin (m4_get_symbol_value (symbol)))
 #define m4_get_symbol_placeholder(symbol)                              \
        (m4_get_symbol_value_placeholder (m4_get_symbol_value (symbol)))
-#define m4_symbol_groks_macro(symbol)                                  \
-       (m4_symbol_value_groks_macro (m4_get_symbol_value (symbol)))
+#define m4_symbol_flatten_args(symbol)                                 \
+       (m4_symbol_value_flatten_args (m4_get_symbol_value (symbol)))
 
 extern m4_symbol_value *m4_symbol_value_create   (void);
 extern void            m4_symbol_value_delete    (m4_symbol_value *);
diff --git a/m4/m4private.h b/m4/m4private.h
index c6cc639..65bcbe0 100644
--- a/m4/m4private.h
+++ b/m4/m4private.h
@@ -366,8 +366,8 @@ extern void m4__push_arg_quote      (m4 *, m4_obstack *, 
m4_macro_args *,
 #  define m4_get_symbol_value_builtin(V) (&(V)->u.builtin->builtin)
 #  define m4_get_symbol_value_placeholder(V)                           \
                                        ((V)->u.u_t.text)
-#  define m4_symbol_value_groks_macro(V) (BIT_TEST ((V)->flags,                
\
-                                                   VALUE_MACRO_ARGS_BIT))
+#  define m4_symbol_value_flatten_args(V)                              \
+  (BIT_TEST ((V)->flags, VALUE_FLATTEN_ARGS_BIT))
 
 #  define m4_set_symbol_value_text(V, T, L, A)                         \
   ((V)->type = M4_SYMBOL_TEXT, (V)->u.u_t.text = (T),                  \
@@ -389,7 +389,7 @@ extern void m4__push_arg_quote      (m4 *, m4_obstack *, 
m4_macro_args *,
    m4_builtin.flags to m4_symbol_arg.flags.  We can use additional
    bits for private use.  */
 
-#define VALUE_MACRO_ARGS_BIT           (1 << 0)
+#define VALUE_FLATTEN_ARGS_BIT         (1 << 0)
 #define VALUE_BLIND_ARGS_BIT           (1 << 1)
 #define VALUE_SIDE_EFFECT_ARGS_BIT     (1 << 2)
 #define VALUE_DELETED_BIT              (1 << 3)
diff --git a/m4/macro.c b/m4/macro.c
index 1894d69..583cb03 100644
--- a/m4/macro.c
+++ b/m4/macro.c
@@ -584,17 +584,14 @@ collect_arguments (m4 *context, const char *name, size_t 
len,
   m4_symbol_value token;
   m4_symbol_value *tokenp;
   bool more_args;
-  bool groks_macro_args;
   m4_macro_args args;
   m4_macro_args *argv;
 
-  groks_macro_args = BIT_TEST (SYMBOL_FLAGS (symbol), VALUE_MACRO_ARGS_BIT);
-
   args.argc = 1;
   args.inuse = false;
   args.wrapper = false;
   args.has_ref = false;
-  args.flatten = !groks_macro_args;
+  args.flatten = m4_symbol_flatten_args (symbol);
   args.has_func = false;
   /* Must copy here, since we are consuming tokens, and since symbol
      table can be changed during argument collection.  */
@@ -618,7 +615,7 @@ collect_arguments (m4 *context, const char *name, size_t 
len,
 
          if ((m4_is_symbol_value_text (tokenp)
               && !m4_get_symbol_value_len (tokenp))
-             || (!groks_macro_args && m4_is_symbol_value_func (tokenp)))
+             || (args.flatten && m4_is_symbol_value_func (tokenp)))
            {
              obstack_free (arguments, tokenp);
              tokenp = &empty_symbol;
diff --git a/m4/module.c b/m4/module.c
index 4b15700..2bda5f6 100644
--- a/m4/module.c
+++ b/m4/module.c
@@ -451,6 +451,8 @@ m4__module_open (m4 *context, const char *name, m4_obstack 
*obs)
                  assert (bp->min_args > 0
                          || (bp->flags & (M4_BUILTIN_BLIND
                                           | M4_BUILTIN_SIDE_EFFECT)) == 0);
+                 assert (bp->max_args
+                         || (bp->flags & M4_BUILTIN_FLATTEN_ARGS) == 0);
                  assert ((bp->flags & ~M4_BUILTIN_FLAGS_MASK) == 0);
 
                  memcpy (&builtin->builtin, bp, sizeof *bp);
diff --git a/m4/symtab.c b/m4/symtab.c
index 8b68132..995495f 100644
--- a/m4/symtab.c
+++ b/m4/symtab.c
@@ -711,12 +711,12 @@ m4_get_symbol_traced (m4_symbol *symbol)
   return symbol->traced;
 }
 
-#undef m4_symbol_value_groks_macro
+#undef m4_symbol_value_flatten_args
 bool
-m4_symbol_value_groks_macro (m4_symbol_value *value)
+m4_symbol_value_flatten_args (m4_symbol_value *value)
 {
   assert (value);
-  return BIT_TEST (value->flags, VALUE_MACRO_ARGS_BIT);
+  return BIT_TEST (value->flags, VALUE_FLATTEN_ARGS_BIT);
 }
 
 #undef m4_get_symbol_value
diff --git a/modules/gnu.c b/modules/gnu.c
index b4c36af..d34cb4b 100644
--- a/modules/gnu.c
+++ b/modules/gnu.c
@@ -61,7 +61,7 @@
 
 
 /* Generate prototypes for each builtin handler function. */
-#define BUILTIN(handler, macros, blind, side, min, max)  M4BUILTIN(handler)
+#define BUILTIN(handler, macros, blind, side, min, max)  M4BUILTIN (handler)
   builtin_functions
 #undef BUILTIN
 
@@ -69,12 +69,9 @@
 /* Generate a table for mapping m4 symbol names to handler functions. */
 m4_builtin m4_builtin_table[] =
 {
-#define BUILTIN(handler, macros, blind, side, min, max)        \
-  { CONC(builtin_, handler), STR(handler),             \
-    ((macros ? M4_BUILTIN_GROKS_MACRO : 0)             \
-     | (blind ? M4_BUILTIN_BLIND : 0)                  \
-     | (side ? M4_BUILTIN_SIDE_EFFECT : 0)),           \
-    min, max },
+#define BUILTIN(handler, macros, blind, side, min, max)                        
\
+  M4BUILTIN_ENTRY (handler, #handler, macros, blind, side, min, max)
+
   builtin_functions
 #undef BUILTIN
 
@@ -448,7 +445,7 @@ M4BUILTIN_HANDLER (builtin)
                            (bp->flags & M4_BUILTIN_SIDE_EFFECT) != 0))
            {
              m4_macro_args *new_argv;
-             bool flatten = (bp->flags & M4_BUILTIN_GROKS_MACRO) == 0;
+             bool flatten = (bp->flags & M4_BUILTIN_FLATTEN_ARGS) != 0;
              new_argv = m4_make_argv_ref (context, argv, name, M4ARGLEN (1),
                                           true, flatten);
              bp->func (context, obs, argc - 1, new_argv);
@@ -683,9 +680,8 @@ M4BUILTIN_HANDLER (indir)
       else
        {
          m4_macro_args *new_argv;
-         bool flatten = !m4_symbol_groks_macro (symbol);
          new_argv = m4_make_argv_ref (context, argv, name, M4ARGLEN (1),
-                                      true, flatten);
+                                      true, m4_symbol_flatten_args (symbol));
          m4_macro_call (context, m4_get_symbol_value (symbol), obs,
                         argc - 1, new_argv);
        }
diff --git a/modules/import.c b/modules/import.c
index 93b197d..8b5fc05 100644
--- a/modules/import.c
+++ b/modules/import.c
@@ -1,5 +1,5 @@
 /* GNU m4 -- A simple macro processor
-   Copyright (C) 2003, 2006, 2007 Free Software Foundation, Inc.
+   Copyright (C) 2003, 2006, 2007, 2008 Free Software Foundation, Inc.
 
    This file is part of GNU M4.
 
@@ -36,18 +36,14 @@
   BUILTIN (symbol_fail,        false,  false,  false,  0,      1)      \
   BUILTIN (module_fail,        false,  false,  false,  0,      1)      \
 
-#define BUILTIN(handler, macros, blind, side, min, max) M4BUILTIN(handler)
+#define BUILTIN(handler, macros, blind, side, min, max) M4BUILTIN (handler)
   builtin_functions
 #undef BUILTIN
 
 m4_builtin m4_builtin_table[] =
 {
-#define BUILTIN(handler, macros, blind, side, min, max)        \
-  { CONC(builtin_, handler), STR(handler),             \
-    ((macros ? M4_BUILTIN_GROKS_MACRO : 0)             \
-     | (blind ? M4_BUILTIN_BLIND : 0)                  \
-     | (side ? M4_BUILTIN_SIDE_EFFECT : 0)),           \
-    min, max },
+#define BUILTIN(handler, macros, blind, side, min, max)                        
\
+  M4BUILTIN_ENTRY (handler, #handler, macros, blind, side, min, max)
 
   builtin_functions
 #undef BUILTIN
diff --git a/modules/load.c b/modules/load.c
index 5ea7242..e169796 100644
--- a/modules/load.c
+++ b/modules/load.c
@@ -42,7 +42,7 @@
 
 
 /* Generate prototypes for each builtin handler function. */
-#define BUILTIN(handler, macros,  blind, side, min, max) M4BUILTIN(handler)
+#define BUILTIN(handler, macros,  blind, side, min, max) M4BUILTIN (handler)
   builtin_functions
 #undef BUILTIN
 
@@ -50,12 +50,9 @@
 /* Generate a table for mapping m4 symbol names to handler functions. */
 m4_builtin m4_builtin_table[] =
 {
-#define BUILTIN(handler, macros, blind, side, min, max)        \
-  { CONC(builtin_, handler), STR(handler),             \
-    ((macros ? M4_BUILTIN_GROKS_MACRO : 0)             \
-     | (blind ? M4_BUILTIN_BLIND : 0)                  \
-     | (side ? M4_BUILTIN_SIDE_EFFECT : 0)),           \
-    min, max },
+#define BUILTIN(handler, macros, blind, side, min, max)                        
\
+  M4BUILTIN_ENTRY (handler, #handler, macros, blind, side, min, max)
+
   builtin_functions
 #undef BUILTIN
 
diff --git a/modules/m4.c b/modules/m4.c
index f5f0766..ca37bef 100644
--- a/modules/m4.c
+++ b/modules/m4.c
@@ -55,10 +55,6 @@ extern const char *m4_expand_ranges (const char *s, 
m4_obstack *obs);
 extern void m4_make_temp     (m4 *context, m4_obstack *obs, const char *macro,
                              const char *name, size_t len, bool dir);
 
-/* stdlib--.h defines mkstemp to a safer replacement, but this
-   interferes with our preprocessor table of builtin definitions.  */
-#undef mkstemp
-
 /* Maintain each of the builtins implemented in this modules along
    with their details in a single table for easy maintenance.
 
@@ -121,12 +117,9 @@ static void        numb_obstack    (m4_obstack *obs, 
number value,
 /* Generate a table for mapping m4 symbol names to handler functions. */
 m4_builtin m4_builtin_table[] =
 {
-#define BUILTIN(handler, macros, blind, side, min, max)        \
-  { CONC (builtin_, handler), STR (handler),           \
-    ((macros ? M4_BUILTIN_GROKS_MACRO : 0)             \
-     | (blind ? M4_BUILTIN_BLIND : 0)                  \
-     | (side ? M4_BUILTIN_SIDE_EFFECT : 0)),           \
-    min, max },
+#define BUILTIN(handler, macros, blind, side, min, max)                        
\
+  M4BUILTIN_ENTRY (handler, #handler, macros, blind, side, min, max)
+
   builtin_functions
 #undef BUILTIN
 
diff --git a/modules/modtest.c b/modules/modtest.c
index f2d39ea..ab23f5f 100644
--- a/modules/modtest.c
+++ b/modules/modtest.c
@@ -40,18 +40,14 @@ extern bool export_test (const char *foo);
 #define builtin_functions                                      \
   BUILTIN (test,       false,  false,  false,  0,      0)
 
-#define BUILTIN(handler, macros, blind, side, min, max) M4BUILTIN(handler)
+#define BUILTIN(handler, macros, blind, side, min, max) M4BUILTIN (handler)
   builtin_functions
 #undef BUILTIN
 
 m4_builtin m4_builtin_table[] =
 {
-#define BUILTIN(handler, macros, blind, side, min, max)        \
-  { CONC(builtin_, handler), STR(handler),             \
-    ((macros ? M4_BUILTIN_GROKS_MACRO : 0)             \
-     | (blind ? M4_BUILTIN_BLIND : 0)                  \
-     | (side ? M4_BUILTIN_SIDE_EFFECT : 0)),           \
-    min, max },
+#define BUILTIN(handler, macros, blind, side, min, max)                        
\
+  M4BUILTIN_ENTRY (handler, #handler, macros, blind, side, min, max)
 
   builtin_functions
 #undef BUILTIN
diff --git a/modules/mpeval.c b/modules/mpeval.c
index ab9a040..7bd2139 100644
--- a/modules/mpeval.c
+++ b/modules/mpeval.c
@@ -100,7 +100,7 @@
 #define numb_decr(n) numb_minus (n, numb_ONE)
 
 /* Generate prototypes for each builtin handler function. */
-#define BUILTIN(handler, macros, blind, side, min, max)  M4BUILTIN(handler)
+#define BUILTIN(handler, macros, blind, side, min, max)  M4BUILTIN (handler)
   builtin_functions
 #undef BUILTIN
 
@@ -109,11 +109,8 @@
 m4_builtin m4_builtin_table[] =
 {
 #define BUILTIN(handler, macros, blind, side, min, max)                        
\
-  { CONC(builtin_, handler), STR(handler),             \
-    ((macros ? M4_BUILTIN_GROKS_MACRO : 0)             \
-     | (blind ? M4_BUILTIN_BLIND : 0)                  \
-     | (side ? M4_BUILTIN_SIDE_EFFECT : 0)),           \
-    min, max },
+  M4BUILTIN_ENTRY (handler, #handler, macros, blind, side, min, max)
+
   builtin_functions
 #undef BUILTIN
 
diff --git a/modules/perl.c b/modules/perl.c
index 61db803..811a114 100644
--- a/modules/perl.c
+++ b/modules/perl.c
@@ -48,12 +48,8 @@
 
 m4_builtin m4_builtin_table[] =
 {
-#define BUILTIN(handler, macros, blind, side, min, max)        \
-  { CONC (builtin_, handler), STR (handler),           \
-    ((macros ? M4_BUILTIN_GROKS_MACRO : 0)             \
-     | (blind ? M4_BUILTIN_BLIND : 0)                  \
-     | (side ? M4_BUILTIN_SIDE_EFFECT : 0)),           \
-    min, max },
+#define BUILTIN(handler, macros, blind, side, min, max)                        
\
+  M4BUILTIN_ENTRY (handler, #handler, macros, blind, side, min, max)
 
   builtin_functions
 #undef BUILTIN
diff --git a/modules/shadow.c b/modules/shadow.c
index 1a15798..db8515e 100644
--- a/modules/shadow.c
+++ b/modules/shadow.c
@@ -38,18 +38,14 @@
   BUILTIN (test,       false,  false,  false,  0,      -1 )    \
 
 
-#define BUILTIN(handler, macros, blind, side, min, max) M4BUILTIN(handler)
+#define BUILTIN(handler, macros, blind, side, min, max) M4BUILTIN (handler)
   builtin_functions
 #undef BUILTIN
 
 m4_builtin m4_builtin_table[] =
 {
-#define BUILTIN(handler, macros, blind, side, min, max)        \
-  { CONC(builtin_, handler), STR(handler),             \
-    ((macros ? M4_BUILTIN_GROKS_MACRO : 0)             \
-     | (blind ? M4_BUILTIN_BLIND : 0)                  \
-     | (side ? M4_BUILTIN_SIDE_EFFECT : 0)),           \
-    min, max },
+#define BUILTIN(handler, macros, blind, side, min, max)                        
\
+  M4BUILTIN_ENTRY (handler, #handler, macros, blind, side, min, max)
 
   builtin_functions
 #undef BUILTIN
diff --git a/modules/stdlib.c b/modules/stdlib.c
index 0849d3d..c24fa2b 100644
--- a/modules/stdlib.c
+++ b/modules/stdlib.c
@@ -57,18 +57,14 @@
     BUILTIN (uname,    false,  false,  false,  0,      0  )    \
 
 
-#define BUILTIN(handler, macros, blind, side, min, max) M4BUILTIN(handler);
+#define BUILTIN(handler, macros, blind, side, min, max) M4BUILTIN (handler);
   builtin_functions
 #undef BUILTIN
 
 m4_builtin m4_builtin_table[] =
 {
-#define BUILTIN(handler, macros, blind, side, min, max)        \
-  { CONC(builtin_, handler), STR(handler),             \
-    ((macros ? M4_BUILTIN_GROKS_MACRO : 0)             \
-     | (blind ? M4_BUILTIN_BLIND : 0)                  \
-     | (side ? M4_BUILTIN_SIDE_EFFECT : 0)),           \
-    min, max },
+#define BUILTIN(handler, macros, blind, side, min, max)                        
\
+  M4BUILTIN_ENTRY (handler, #handler, macros, blind, side, min, max)
 
   builtin_functions
 #undef BUILTIN
diff --git a/modules/time.c b/modules/time.c
index 9807ae0..4febc8c 100644
--- a/modules/time.c
+++ b/modules/time.c
@@ -1,5 +1,6 @@
 /* GNU m4 -- A simple macro processor
-   Copyright (C) 1999, 2000, 2001, 2006, 2007 Free Software Foundation, Inc.
+   Copyright (C) 1999, 2000, 2001, 2006, 2007, 2008 Free Software
+   Foundation, Inc.
 
    This file is part of GNU M4.
 
@@ -50,7 +51,7 @@
   BUILTIN (strftime,   false,  true,   false,  2,      2  )    \
 
 
-#define BUILTIN(handler, macros, blind, side, min, max)  M4BUILTIN(handler)
+#define BUILTIN(handler, macros, blind, side, min, max)  M4BUILTIN (handler)
   builtin_functions
 # if HAVE_MKTIME
   mktime_functions
@@ -62,12 +63,8 @@
 
 m4_builtin m4_builtin_table[] =
 {
-#define BUILTIN(handler, macros, blind, side, min, max)        \
-  { CONC(builtin_, handler), STR(handler),             \
-    ((macros ? M4_BUILTIN_GROKS_MACRO : 0)             \
-     | (blind ? M4_BUILTIN_BLIND : 0)                  \
-     | (side ? M4_BUILTIN_SIDE_EFFECT : 0)),           \
-    min, max },
+#define BUILTIN(handler, macros, blind, side, min, max)                        
\
+  M4BUILTIN_ENTRY (handler, #handler, macros, blind, side, min, max)
 
   builtin_functions
 # if HAVE_MKTIME
-- 
1.5.4


>From a2df6b461c098df5a505d79d119538b3a294e301 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Thu, 13 Mar 2008 21:01:39 -0600
Subject: [PATCH] Stage 19c: allow builtin tokens in more macros.

* m4/m4module.h (m4_symbol_value_lookup, m4_builtin_print): New
prototypes.
* m4/m4private.h (enum m4__symbol_chain_type): Add
M4__CHAIN_FUNC.
(struct m4__symbol_chain): Add builtin member.
* m4/utility.c (m4_symbol_value_lookup): New method.
* m4/builtin.c (m4_builtin_print): New function.
* m4/symtab.c (m4_symbol_value_print): Use it.
* m4/input.c (builtin_print): Likewise.
(m4__push_symbol): Allow pushing builtin tokens.
(composite_peek, composite_read, composite_unget, composite_clean)
(composite_print): Handle builtin tokens.
(init_builtin_token): Allow builtin tokens from composite input.
(m4__next_token): Flatten builtins inside comment or string.
* m4/macro.c (expand_argument): Strengthen assertion.
(collect_arguments, m4_arg_equal, m4_arg_print, m4_push_args):
Handle builtin tokens.
(arg_symbol): Add parameter, and allow NULL level.
(m4_arg_symbol, m4__push_arg_quote): Adjust callers.
(m4_arg_text): Ensure all builtins have been flattened.
* modules/m4.c (defn, dumpdef, popdef, traceoff, traceon)
(undefine, m4_dump_symbols): Warn on invalid macro names.
(ifdef, ifelse, shift): Handle builtin tokens.
* modules/gnu.c (m4symbols): Likewise.
* doc/m4.texinfo (Defn, Ifdef, Ifelse): Document and test the new
behavior.
(Debuglen): Likewise, and remove xfail.
* NEWS: Mention the change.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog      |   36 ++++++++++
 NEWS           |   11 +++
 doc/m4.texinfo |   83 ++++++++++++++++++++++--
 m4/builtin.c   |   32 +++++++++
 m4/input.c     |  201 ++++++++++++++++++++++++++++++++++++++++++++------------
 m4/m4module.h  |    6 ++-
 m4/m4private.h |    3 +-
 m4/macro.c     |   95 +++++++++++++++++---------
 m4/symtab.c    |   27 +++-----
 m4/utility.c   |   21 ++++++
 modules/gnu.c  |    2 +-
 modules/m4.c   |   76 +++++++++++----------
 12 files changed, 454 insertions(+), 139 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 147ea10..8ac919e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,41 @@
 2008-03-13  Eric Blake  <address@hidden>
 
+       Stage 19c: allow builtin tokens in more macros.
+       Allow builtin tokens inside symbol chains, although for now, they
+       are not allowed inside comments or quotes.  Enable builtin token
+       handling in more macros, if only to consistently diagnose invalid
+       macro names.
+       Memory impact: none.
+       Speed impact: slight impact, due to more bookkeeping.
+       * m4/m4module.h (m4_symbol_value_lookup, m4_builtin_print): New
+       prototypes.
+       * m4/m4private.h (enum m4__symbol_chain_type): Add
+       M4__CHAIN_FUNC.
+       (struct m4__symbol_chain): Add builtin member.
+       * m4/utility.c (m4_symbol_value_lookup): New method.
+       * m4/builtin.c (m4_builtin_print): New function.
+       * m4/symtab.c (m4_symbol_value_print): Use it.
+       * m4/input.c (builtin_print): Likewise.
+       (m4__push_symbol): Allow pushing builtin tokens.
+       (composite_peek, composite_read, composite_unget, composite_clean)
+       (composite_print): Handle builtin tokens.
+       (init_builtin_token): Allow builtin tokens from composite input.
+       (m4__next_token): Flatten builtins inside comment or string.
+       * m4/macro.c (expand_argument): Strengthen assertion.
+       (collect_arguments, m4_arg_equal, m4_arg_print, m4_push_args):
+       Handle builtin tokens.
+       (arg_symbol): Add parameter, and allow NULL level.
+       (m4_arg_symbol, m4__push_arg_quote): Adjust callers.
+       (m4_arg_text): Ensure all builtins have been flattened.
+       * modules/m4.c (defn, dumpdef, popdef, traceoff, traceon)
+       (undefine, m4_dump_symbols): Warn on invalid macro names.
+       (ifdef, ifelse, shift): Handle builtin tokens.
+       * modules/gnu.c (m4symbols): Likewise.
+       * doc/m4.texinfo (Defn, Ifdef, Ifelse): Document and test the new
+       behavior.
+       (Debuglen): Likewise, and remove xfail.
+       * NEWS: Mention the change.
+
        Stage 19b: invert sense of bit for handling builtin tokens.
        Pass builtin tokens by default, rather than as the exception, so
        that the logic can consistently refer to flattening arguments.
diff --git a/NEWS b/NEWS
index 74fb22e..6e2fa40 100644
--- a/NEWS
+++ b/NEWS
@@ -182,6 +182,9 @@ promoted to 2.0.
     the recommendation of POSIX to obsolete `maketemp' as inherently
     insecure when obeying POSIX.
 
+*** The `m4symbols' builtin now warns if given a builtin token instead of
+    a macro name.  It remains silent for undefined macros.
+
 *** The `patsubst' and `regexp' builtins have a new optional 4th argument
     to use a different regular expression syntax for the duration of that
     invocation.
@@ -213,6 +216,14 @@ promoted to 2.0.
 ** Fix regression introduced in 1.4.10b where using `builtin' or `indir'
    to perform nested `shift' calls triggered an assertion failure.
 
+** Enhance the `ifdef', `ifelse', and `shift' builtins, as well as all
+   user macros, to transparently handle builtin tokens generated by `defn'.
+
+** Enhance the `defn', `dumpdef', `ifdef', `popdef', `traceon', `traceoff',
+   and `undefine' macros to warn when encountering a builtin token in the
+   context of a macro name, rather than acting on the empty string.  This
+   was already done for `define', `pushdef', `builtin', and `indir'.
+
 * Noteworthy changes in Version 1.4.10b (2008-02-25) [beta]
   Released by Eric Blake, based on git version 1.4.10a
 
diff --git a/doc/m4.texinfo b/doc/m4.texinfo
index 018c9c8..6d024b3 100644
--- a/doc/m4.texinfo
+++ b/doc/m4.texinfo
@@ -2277,9 +2277,14 @@ defn([l], [r])
 
 @cindex builtins, special tokens
 @cindex tokens, builtin macro
-Using @code{defn} to generate special tokens for builtin macros outside
-of expected contexts can sometimes trigger warnings.  But most of the
-time, such tokens are silently converted to the empty string.
+Using @code{defn} to generate special tokens for builtin macros will
+generate a warning in contexts where a macro name is expected.  But in
+contexts that operate on text, the builtin token is just silently
+converted to an empty string.  As of M4 1.4.11, expansion of user macros
+will also preserve builtin tokens.  However, any use of builtin tokens
+outside of the second argument to @code{define} and @code{pushdef} is
+generally not portable, since earlier @acronym{GNU} M4 versions, as well
+as other @code{m4} implementations, vary on how such tokens are treated.
 
 @example
 $ @kbd{m4 -d}
@@ -2294,9 +2299,43 @@ len(defn(`divnum'))
 @result{}0
 define(`echo', `$@@')
 @result{}
-define(`mydivnum', echo(defn(`divnum')))
+define(`mydivnum', shift(echo(`', defn(`divnum'))))
 @result{}
 mydivnum
address@hidden
+define(`', `empty-$1')
address@hidden
+defn(defn(`divnum'))
address@hidden:stdin:9: Warning: defn: invalid macro name ignored
address@hidden
+pushdef(defn(`divnum'), `oops')
address@hidden:stdin:10: Warning: pushdef: invalid macro name ignored
address@hidden
+traceon(defn(`divnum'))
address@hidden:stdin:11: Warning: traceon: invalid macro name ignored
address@hidden
+indir(defn(`divnum'), `string')
address@hidden:stdin:12: Warning: indir: invalid macro name ignored
address@hidden
+indir(`', `string')
address@hidden
+traceoff(defn(`divnum'))
address@hidden:stdin:14: Warning: traceoff: invalid macro name ignored
address@hidden
+popdef(defn(`divnum'))
address@hidden:stdin:15: Warning: popdef: invalid macro name ignored
address@hidden
+dumpdef(defn(`divnum'))
address@hidden:stdin:16: Warning: dumpdef: invalid macro name ignored
address@hidden
+undefine(defn(`divnum'))
address@hidden:stdin:17: Warning: undefine: invalid macro name ignored
address@hidden
+dumpdef(`')
address@hidden:@tabchar{}`empty-$1'
address@hidden
+m4symbols(defn(`divnum'))
address@hidden:stdin:19: Warning: m4symbols: invalid macro name ignored
 @result{}
 @end example
 
@@ -2854,6 +2893,22 @@ ifdef(`no_such_macro', `yes', `no', `extra argument')
 @result{}no
 @end example
 
+As of M4 1.4.11, @code{ifdef} transparently handles builtin tokens
+generated by @code{defn} (@pxref{Defn}) that occur in either
address@hidden, although a warning is issued for invalid macro names.
+
address@hidden
+define(`', `empty')
address@hidden
+ifdef(defn(`defn'), `yes', `no')
address@hidden:stdin:2: Warning: ifdef: invalid macro name ignored
address@hidden
+define(`foo', ifdef(`divnum', defn(`divnum'), `undefined'))
address@hidden
+foo
address@hidden
address@hidden example
+
 @node Ifelse
 @section If-else construct, or multibranch
 
@@ -2954,6 +3009,24 @@ ifelse(`foo', `bar', `3', `gnu', `gnats', `6', `7', `8')
 @result{}7
 @end example
 
+As of M4 1.4.11, @code{ifelse} transparently handles builtin tokens
+generated by @code{defn} (@pxref{Defn}).  Because of this, it is always
+safe to compare two macro definitions, without worrying whether the
+macro might be a builtin.
+
address@hidden
+ifelse(defn(`defn'), `', `yes', `no')
address@hidden
+ifelse(defn(`defn'), defn(`divnum'), `yes', `no')
address@hidden
+ifelse(defn(`defn'), defn(`defn'), `yes', `no')
address@hidden
+define(`foo', ifelse(`', `', defn(`divnum')))
address@hidden
+foo
address@hidden
address@hidden example
+
 Naturally, the normal case will be slightly more advanced than these
 examples.  A common use of @code{ifelse} is in macros implementing loops
 of various kinds.
@@ -3883,7 +3956,7 @@ echo(`1', `long string')
 @result{}1,long string
 echo(defn(`changequote'))
 @error{}m4trace: -2- defn(`change...') -> <changequote>
address@hidden: -1- echo(<changequote>) -> ``''
address@hidden: -1- echo(<changequote>) -> ``<changequote>''
 @result{}
 debuglen
 @result{}debuglen
diff --git a/m4/builtin.c b/m4/builtin.c
index 7f9d957..5228abc 100644
--- a/m4/builtin.c
+++ b/m4/builtin.c
@@ -85,3 +85,35 @@ m4_builtin_find_by_func (m4_module *module, m4_builtin_func 
*func)
 
   return 0;
 }
+
+/* Print a representation of FUNC to OBS, optionally including the
+   MODULE it came from.  If FLATTEN, output QUOTES around an empty
+   string instead.  */
+void
+m4_builtin_print (m4_obstack *obs, const m4_builtin *func, bool flatten,
+                 const m4_string_pair *quotes, m4_module *module)
+{
+  assert (func);
+  if (flatten)
+    {
+      if (quotes)
+       {
+         obstack_grow (obs, quotes->str1, quotes->len1);
+         obstack_grow (obs, quotes->str2, quotes->len2);
+       }
+      module = NULL;
+    }
+  else
+    {
+      obstack_1grow (obs, '<');
+      obstack_grow (obs, func->name, strlen (func->name));
+      obstack_1grow (obs, '>');
+    }
+  if (module)
+    {
+      const char *text = m4_get_module_name (module);
+      obstack_1grow (obs, '{');
+      obstack_grow (obs, text, strlen (text));
+      obstack_1grow (obs, '}');
+    }
+}
diff --git a/m4/input.c b/m4/input.c
index c337ca8..7d27dad 100644
--- a/m4/input.c
+++ b/m4/input.c
@@ -409,20 +409,9 @@ builtin_unget (m4_input_block *me, int ch)
 static void
 builtin_print (m4_input_block *me, m4 *context, m4_obstack *obs)
 {
-  const m4__builtin *bp = me->u.builtin;
-  const char *text = bp->builtin.name;
-
-  assert (bp);
-  obstack_1grow (obs, '<');
-  obstack_grow (obs, text, strlen (text));
-  obstack_1grow (obs, '>');
-  if (m4_is_debug_bit (context, M4_DEBUG_TRACE_MODULE))
-    {
-      text = m4_get_module_name (bp->module);
-      obstack_1grow (obs, '{');
-      obstack_grow (obs, text, strlen (text));
-      obstack_1grow (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
@@ -554,11 +543,7 @@ m4__push_symbol (m4 *context, m4_symbol_value *value, 
size_t level, bool inuse)
          return false;
        }
     }
-  else if (m4_is_symbol_value_func (value))
-    {
-      /* TODO - use the builtin, rather than flattening it.  */
-    }
-  else
+  else if (!m4_is_symbol_value_func (value))
     {
       /* For composite values, if argv is already in use, creating
         additional references for long text segments is more
@@ -605,8 +590,24 @@ 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 (level == SIZE_MAX)
        {
          /* Nothing to copy, since link already lives on obstack.  */
@@ -718,6 +719,10 @@ composite_peek (m4_input_block *me, m4 *context, bool 
allow_argv)
          if (chain->u.u_s.len)
            return to_uchar (chain->u.u_s.str[0]);
          break;
+       case M4__CHAIN_FUNC:
+         if (chain->u.builtin)
+           return CHAR_BUILTIN;
+         break;
        case M4__CHAIN_ARGV:
          argc = m4_arg_argc (chain->u.u_a.argv);
          if (chain->u.u_a.index == argc)
@@ -772,6 +777,10 @@ composite_read (m4_input_block *me, m4 *context, bool 
allow_quote, bool safe)
          if (chain->u.u_s.level < SIZE_MAX)
            m4__adjust_refcount (context, chain->u.u_s.level, false);
          break;
+       case M4__CHAIN_FUNC:
+         if (chain->u.builtin)
+           return CHAR_BUILTIN;
+         break;
        case M4__CHAIN_ARGV:
          if (chain->u.u_a.index == m4_arg_argc (chain->u.u_a.argv))
            {
@@ -816,6 +825,9 @@ composite_unget (m4_input_block *me, int ch)
       chain->u.u_s.str--;
       chain->u.u_s.len++;
       break;
+    case M4__CHAIN_FUNC:
+      assert (ch == CHAR_BUILTIN && chain->u.builtin);
+      break;
     case M4__CHAIN_ARGV:
       /* FIXME - support M4_SYNTAX_COMMA.  */
       assert (ch == ',' && !chain->u.u_a.comma);
@@ -845,6 +857,10 @@ composite_clean (m4_input_block *me, m4 *context, bool 
cleanup)
          if (chain->u.u_s.level < SIZE_MAX)
            m4__adjust_refcount (context, chain->u.u_s.level, false);
          break;
+       case M4__CHAIN_FUNC:
+         if (chain->u.builtin)
+           return false;
+         break;
        case M4__CHAIN_ARGV:
          if (chain->u.u_a.index < m4_arg_argc (chain->u.u_a.argv))
            {
@@ -883,6 +899,10 @@ composite_print (m4_input_block *me, m4 *context, 
m4_obstack *obs)
                                       chain->u.u_s.len, NULL, &maxlen))
            done = true;
          break;
+       case M4__CHAIN_FUNC:
+         m4_builtin_print (obs, &chain->u.builtin->builtin, false, NULL,
+                           module ? chain->u.builtin->module : NULL);
+         break;
        case M4__CHAIN_ARGV:
          assert (!chain->u.u_a.comma);
          if (m4_arg_print (context, obs, chain->u.u_a.argv,
@@ -1060,17 +1080,32 @@ m4_pop_wrapup (m4 *context)
 }
 
 /* Populate TOKEN with the builtin token at the top of the input
-   stack, then consume the input.  */
+   stack, then consume the input.  If TOKEN is NULL, discard the
+   builtin token instead.  */
 static void
 init_builtin_token (m4 *context, m4_symbol_value *token)
 {
   int ch = next_char (context, false, true);
-  m4_input_block *block = isp;
-  assert (ch == CHAR_BUILTIN && block->funcs->read_func == builtin_read
-         && block->u.builtin);
+  assert (ch == CHAR_BUILTIN);
 
-  m4__set_symbol_value_builtin (token, block->u.builtin);
-  block->u.builtin = NULL;
+  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;
+    }
 }
 
 /* When a QUOTE token is seen, convert VALUE to a composite (if it is
@@ -1526,12 +1561,41 @@ m4__next_token (m4 *context, m4_symbol_value *token, 
int *line,
        if (obs)
          obs_safe = obs;
        quote_level = 1;
+       type = M4_TOKEN_STRING;
        while (1)
          {
            ch = next_char (context, obs && m4__quote_age (M4SYNTAX), true);
            if (ch == CHAR_EOF)
              m4_error_at_line (context, EXIT_FAILURE, 0, file, *line, caller,
                                _("end of file in string"));
+           if (ch == CHAR_BUILTIN)
+             {
+               /* TODO support concatenation of builtins.  */
+               if (obstack_object_size (obs_safe) == 0
+                   && token->type == M4_SYMBOL_VOID)
+                 {
+                   /* Strip quotes if they surround a lone builtin
+                      token.  */
+                   assert (quote_level == 1);
+                   init_builtin_token (context, token);
+                   ch = peek_char (context, false);
+                   if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_RQUOTE))
+                     {
+                       ch = next_char (context, false, true);
+#ifdef DEBUG_INPUT
+                       m4_print_token (context, "next_token", M4_TOKEN_MACDEF,
+                                       token);
+#endif
+                       return M4_TOKEN_MACDEF;
+                     }
+                   token->type = M4_SYMBOL_VOID;
+                 }
+               else
+                 init_builtin_token (context, NULL);
+               m4_warn_at_line (context, 0, file, *line, caller,
+                                _("cannot quote builtin"));
+               continue;
+             }
            if (ch == CHAR_QUOTE)
              append_quote_token (context, obs, token);
            else if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_RQUOTE))
@@ -1548,7 +1612,6 @@ m4__next_token (m4 *context, m4_symbol_value *token, int 
*line,
            else
              obstack_1grow (obs_safe, ch);
          }
-       type = M4_TOKEN_STRING;
       }
     else if (!m4_is_syntax_single_quotes (M4SYNTAX)
             && MATCH (context, ch, context->syntax->quote.str1, true))
@@ -1556,6 +1619,7 @@ m4__next_token (m4 *context, m4_symbol_value *token, int 
*line,
        if (obs)
          obs_safe = obs;
        quote_level = 1;
+       type = M4_TOKEN_STRING;
        assert (!m4__quote_age (M4SYNTAX));
        while (1)
          {
@@ -1563,6 +1627,36 @@ m4__next_token (m4 *context, m4_symbol_value *token, int 
*line,
            if (ch == CHAR_EOF)
              m4_error_at_line (context, EXIT_FAILURE, 0, file, *line, caller,
                                _("end of file in string"));
+           if (ch == CHAR_BUILTIN)
+             {
+               /* TODO support concatenation of builtins.  */
+               if (obstack_object_size (obs_safe) == 0
+                   && token->type == M4_SYMBOL_VOID)
+                 {
+                   /* Strip quotes if they surround a lone builtin
+                      token.  */
+                   assert (quote_level == 1);
+                   init_builtin_token (context, token);
+                   ch = peek_char (context, false);
+                   if (MATCH (context, ch, context->syntax->quote.str2,
+                              false))
+                     {
+                       ch = next_char (context, false, true);
+                       MATCH (context, ch, context->syntax->quote.str2, true);
+#ifdef DEBUG_INPUT
+                       m4_print_token (context, "next_token", M4_TOKEN_MACDEF,
+                                       token);
+#endif
+                       return M4_TOKEN_MACDEF;
+                     }
+                   token->type = M4_SYMBOL_VOID;
+                 }
+               else
+                 init_builtin_token (context, NULL);
+               m4_warn_at_line (context, 0, file, *line, caller,
+                                _("cannot quote builtin"));
+               continue;
+             }
            if (MATCH (context, ch, context->syntax->quote.str2, true))
              {
                if (--quote_level == 0)
@@ -1579,24 +1673,34 @@ m4__next_token (m4 *context, m4_symbol_value *token, 
int *line,
            else
              obstack_1grow (obs_safe, ch);
          }
-       type = M4_TOKEN_STRING;
       }
     else if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_BCOMM))
       {                                        /* COMMENT, SHORT DELIM */
        if (obs && !m4_get_discard_comments_opt (context))
          obs_safe = obs;
        obstack_1grow (obs_safe, ch);
-       while ((ch = next_char (context, false, true)) < CHAR_EOF
-              && !m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_ECOMM))
-         obstack_1grow (obs_safe, ch);
-       if (ch != CHAR_EOF)
+       while (1)
          {
+           ch = next_char (context, false, true);
+           if (ch == CHAR_EOF)
+             m4_error_at_line (context, EXIT_FAILURE, 0, file, *line, caller,
+                               _("end of file in comment"));
+           if (ch == CHAR_BUILTIN)
+             {
+               /* TODO support concatenation of builtins.  */
+               m4_warn_at_line (context, 0, file, *line, caller,
+                                _("cannot comment builtin"));
+               init_builtin_token (context, NULL);
+               continue;
+             }
+           if (m4_has_syntax (M4SYNTAX, ch, M4_SYNTAX_ECOMM))
+             {
+               obstack_1grow (obs_safe, ch);
+               break;
+             }
            assert (ch < CHAR_EOF);
            obstack_1grow (obs_safe, ch);
          }
-       else
-         m4_error_at_line (context, EXIT_FAILURE, 0, file, *line, caller,
-                           _("end of file in comment"));
        type = (m4_get_discard_comments_opt (context)
                ? M4_TOKEN_NONE : M4_TOKEN_STRING);
       }
@@ -1607,18 +1711,29 @@ m4__next_token (m4 *context, m4_symbol_value *token, 
int *line,
          obs_safe = obs;
        obstack_grow (obs_safe, context->syntax->comm.str1,
                      context->syntax->comm.len1);
-       while ((ch = next_char (context, false, true)) < CHAR_EOF
-              && !MATCH (context, ch, context->syntax->comm.str2, true))
-         obstack_1grow (obs_safe, ch);
-       if (ch != CHAR_EOF)
+       while (1)
          {
+           ch = next_char (context, false, true);
+           if (ch == CHAR_EOF)
+             m4_error_at_line (context, EXIT_FAILURE, 0, file, *line, caller,
+                               _("end of file in comment"));
+           if (ch == CHAR_BUILTIN)
+             {
+               /* TODO support concatenation of builtins.  */
+               m4_warn_at_line (context, 0, file, *line, caller,
+                                _("cannot comment builtin"));
+               init_builtin_token (context, NULL);
+               continue;
+             }
+           if (MATCH (context, ch, context->syntax->comm.str2, true))
+             {
+               obstack_grow (obs_safe, context->syntax->comm.str2,
+                             context->syntax->comm.len2);
+               break;
+             }
            assert (ch < CHAR_EOF);
-           obstack_grow (obs_safe, context->syntax->comm.str2,
-                         context->syntax->comm.len2);
+           obstack_1grow (obs_safe, ch);
          }
-       else
-         m4_error_at_line (context, EXIT_FAILURE, 0, file, *line, caller,
-                           _("end of file in comment"));
        type = (m4_get_discard_comments_opt (context)
                ? M4_TOKEN_NONE : M4_TOKEN_STRING);
       }
diff --git a/m4/m4module.h b/m4/m4module.h
index 0941d2b..6cbe185 100644
--- a/m4/m4module.h
+++ b/m4/m4module.h
@@ -34,6 +34,7 @@ BEGIN_C_DECLS
 typedef struct m4              m4;
 typedef struct m4_builtin      m4_builtin;
 typedef struct m4_macro                m4_macro;
+typedef struct m4_symbol       m4_symbol;
 typedef struct m4_symbol_value m4_symbol_value;
 typedef struct m4_input_block  m4_input_block;
 typedef struct m4_module       m4_module;
@@ -164,6 +165,8 @@ extern bool m4_bad_argc        (m4 *, int, const char *, 
size_t, size_t,
                                    bool);
 extern bool    m4_numeric_arg     (m4 *, const char *, const char *, int *);
 extern bool    m4_parse_truth_arg (m4 *, const char *, const char *, bool);
+extern m4_symbol *m4_symbol_value_lookup (m4 *, const char *,
+                                         m4_symbol_value *, bool);
 
 /* Error handling.  */
 extern void m4_error (m4 *, int, int, const char *, const char *, ...)
@@ -186,7 +189,6 @@ extern void         m4_set_exit_failure (int);
 
 typedef struct m4_syntax_table m4_syntax_table;
 typedef struct m4_symbol_table m4_symbol_table;
-typedef struct m4_symbol       m4_symbol;
 
 extern m4 *            m4_create       (void);
 extern void            m4_delete       (m4 *);
@@ -342,6 +344,8 @@ 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 *);
 
 
 
diff --git a/m4/m4private.h b/m4/m4private.h
index 65bcbe0..5ff7c95 100644
--- a/m4/m4private.h
+++ b/m4/m4private.h
@@ -211,7 +211,7 @@ struct m4_symbol
 enum m4__symbol_chain_type
 {
   M4__CHAIN_STR,       /* Link contains a string, u.u_s is valid.  */
-  /* TODO Add M4__CHAIN_FUNC.  */
+  M4__CHAIN_FUNC,      /* Link contains builtin token, u.builtin is valid.  */
   M4__CHAIN_ARGV       /* Link contains a $@ reference, u.u_a is valid.  */
 };
 
@@ -229,6 +229,7 @@ struct m4__symbol_chain
       size_t len;              /* Remaining length of str.  */
       size_t level;            /* Expansion level of content, or SIZE_MAX.  */
     } u_s;                     /* M4__CHAIN_STR.  */
+    const m4__builtin *builtin;        /* M4__CHAIN_FUNC.  */
     struct
     {
       m4_macro_args *argv;             /* Reference to earlier address@hidden  
*/
diff --git a/m4/macro.c b/m4/macro.c
index 583cb03..d03f551 100644
--- a/m4/macro.c
+++ b/m4/macro.c
@@ -401,6 +401,7 @@ expand_argument (m4 *context, m4_obstack *obs, 
m4_symbol_value *argp,
          assert (paren_level == 0 && argp->type == M4_SYMBOL_VOID
                  && obstack_object_size (obs) == 0
                  && token.u.u_c.chain == token.u.u_c.end
+                 && token.u.u_c.chain->quote_age == age
                  && token.u.u_c.chain->type == M4__CHAIN_ARGV);
          argp->type = M4_SYMBOL_COMP;
          argp->u.u_c.chain = argp->u.u_c.end = token.u.u_c.chain;
@@ -662,7 +663,8 @@ 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;
-  if (args.quote_age != m4__quote_age (M4SYNTAX))
+  /* TODO allow funcs without crippling quote age.  */
+  if (args.quote_age != m4__quote_age (M4SYNTAX) || args.has_func)
     argv->quote_age = 0;
   argv->arraylen = args.arraylen;
   return argv;
@@ -1147,19 +1149,27 @@ make_argv_ref (m4 *context, m4_symbol_value *value, 
m4_obstack *obs,
 
 /* Given ARGV, return the symbol value at the specified INDEX, which
    must be non-zero.  *LEVEL is set to the obstack level that contains
-   the symbol (which is not necessarily the level of ARGV).  */
+   the symbol (which is not necessarily the level of ARGV).  If
+   FLATTEN, avoid returning a builtin token.  */
 static m4_symbol_value *
-arg_symbol (m4_macro_args *argv, size_t index, size_t *level)
+arg_symbol (m4_macro_args *argv, size_t index, size_t *level, bool flatten)
 {
   size_t i;
   m4_symbol_value *value;
 
   assert (index);
-  *level = argv->level;
+  if (level)
+    *level = argv->level;
+  flatten |= argv->flatten;
   if (argv->argc <= index)
     return &empty_symbol;
   if (!argv->wrapper)
-    return argv->array[index - 1];
+    {
+      value = argv->array[index - 1];
+      if (flatten && m4_is_symbol_value_func (value))
+       value = &empty_symbol;
+      return value;
+    }
 
   /* Must cycle through all array slots until we find index, since
      wrappers can contain multiple arguments.  */
@@ -1174,9 +1184,8 @@ arg_symbol (m4_macro_args *argv, size_t index, size_t 
*level)
                        - chain->u.u_a.skip_last))
            {
              value = arg_symbol (chain->u.u_a.argv,
-                                 chain->u.u_a.index - 1 + index, level);
-             if (chain->u.u_a.flatten && m4_is_symbol_value_func (value))
-               value = &empty_symbol;
+                                 chain->u.u_a.index - 1 + index, level,
+                                 flatten || chain->u.u_a.flatten);
              break;
            }
          index -= (chain->u.u_a.argv->argc - chain->u.u_a.index
@@ -1193,8 +1202,7 @@ arg_symbol (m4_macro_args *argv, size_t index, size_t 
*level)
 m4_symbol_value *
 m4_arg_symbol (m4_macro_args *argv, size_t index)
 {
-  size_t dummy;
-  return arg_symbol (argv, index, &dummy);
+  return arg_symbol (argv, index, NULL, false);
 }
 
 /* Given ARGV, return true if argument INDEX is text.  Index 0 is
@@ -1257,7 +1265,8 @@ m4_arg_text (m4 *context, m4_macro_args *argv, size_t 
index)
          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);
+                       argv->flatten || chain->u.u_a.flatten, NULL, NULL,
+                       false, false);
          break;
        default:
          assert (!"m4_arg_text");
@@ -1295,30 +1304,45 @@ m4_arg_equal (m4 *context, m4_macro_args *argv, size_t 
indexa, size_t indexb)
                       m4_get_symbol_value_len (sa)) == 0);
 
   /* Convert both arguments to chains, if not one already.  */
-  /* TODO - allow builtin tokens in the comparison?  */
-  if (m4_is_symbol_value_text (sa))
+  switch (sa->type)
     {
+    case M4_SYMBOL_TEXT:
       tmpa.next = NULL;
       tmpa.type = M4__CHAIN_STR;
       tmpa.u.u_s.str = m4_get_symbol_value_text (sa);
       tmpa.u.u_s.len = m4_get_symbol_value_len (sa);
-    }
-  else
-    {
-      assert (sa->type == M4_SYMBOL_COMP);
+      break;
+    case M4_SYMBOL_FUNC:
+      tmpa.next = NULL;
+      tmpa.type = M4__CHAIN_FUNC;
+      tmpa.u.builtin = sa->u.builtin;
+      break;
+    case M4_SYMBOL_COMP:
       ca = sa->u.u_c.chain;
+      break;
+    default:
+      assert (!"m4_arg_equal");
+      abort ();
     }
-  if (m4_is_symbol_value_text (sb))
+  switch (sb->type)
     {
+    case M4_SYMBOL_TEXT:
       tmpb.next = NULL;
       tmpb.type = M4__CHAIN_STR;
       tmpb.u.u_s.str = m4_get_symbol_value_text (sb);
       tmpb.u.u_s.len = m4_get_symbol_value_len (sb);
-    }
-  else
-    {
-      assert (sb->type == M4_SYMBOL_COMP);
+      break;
+    case M4_SYMBOL_FUNC:
+      tmpb.next = NULL;
+      tmpb.type = M4__CHAIN_FUNC;
+      tmpb.u.builtin = sb->u.builtin;
+      break;
+    case M4_SYMBOL_COMP:
       cb = sb->u.u_c.chain;
+      break;
+    default:
+      assert (!"m4_arg_equal");
+      abort ();
     }
 
   /* Compare each link of the chain.  */
@@ -1356,6 +1380,14 @@ m4_arg_equal (m4 *context, m4_macro_args *argv, size_t 
indexa, size_t indexb)
          cb = &tmpb;
          continue;
        }
+      if (ca->type == M4__CHAIN_FUNC)
+       {
+         if (cb->type != M4__CHAIN_FUNC || ca->u.builtin != cb->u.builtin)
+           return false;
+         ca = ca->next;
+         cb = cb->next;
+         continue;
+       }
       assert (ca->type == M4__CHAIN_STR && cb->type == M4__CHAIN_STR);
       if (ca->u.u_s.len == cb->u.u_s.len)
        {
@@ -1507,9 +1539,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, m4_arg_symbol (argv, i), 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,
+                                &len, module))
        return true;
       if (quotes && !quote_each
          && m4_shipout_string_trunc (obs, quotes->str2, quotes->len2, NULL,
@@ -1591,18 +1623,16 @@ m4_push_arg (m4 *context, m4_obstack *obs, 
m4_macro_args *argv, size_t index)
     m4__push_arg_quote (context, obs, argv, index, NULL);
 }
 
-/* Push argument INDEX from ARGV, which must be a text token, onto the
-   expansion stack OBS for rescanning.  INDEX must be non-zero.
-   QUOTES determines any quote delimiters that were in effect when the
-   reference was created.  */
+/* Push argument INDEX from ARGV onto the expansion stack OBS for
+   rescanning.  INDEX must be non-zero.  QUOTES determines any quote
+   delimiters that were in effect when the reference was created.  */
 void
 m4__push_arg_quote (m4 *context, m4_obstack *obs, m4_macro_args *argv,
                    size_t index, const m4_string_pair *quotes)
 {
   size_t level;
-  m4_symbol_value *value = arg_symbol (argv, index, &level);
+  m4_symbol_value *value = arg_symbol (argv, index, &level, false);
 
-  /* TODO handle builtin tokens?  */
   if (quotes)
     obstack_grow (obs, quotes->str1, quotes->len1);
   if (value != &empty_symbol
@@ -1633,8 +1663,7 @@ m4_push_args (m4 *context, m4_obstack *obs, m4_macro_args 
*argv, bool skip,
       return;
     }
 
-  /* TODO allow shift, $@, to push builtins without flatten.  */
-  value = make_argv_ref (context, &tmp, obs, -1, argv, i, true,
+  value = make_argv_ref (context, &tmp, obs, -1, argv, i, argv->flatten,
                         quote ? quotes : NULL);
   assert (value == &tmp);
   if (m4__push_symbol (context, value, -1, argv->inuse))
diff --git a/m4/symtab.c b/m4/symtab.c
index 995495f..7fa7ccc 100644
--- a/m4/symtab.c
+++ b/m4/symtab.c
@@ -555,7 +555,6 @@ m4_symbol_value_print (m4 *context, m4_symbol_value *value, 
m4_obstack *obs,
                       size_t *maxlen, bool module)
 {
   const char *text;
-  const m4_builtin *bp;
   m4__symbol_chain *chain;
   size_t len = maxlen ? *maxlen : SIZE_MAX;
   bool result = false;
@@ -569,22 +568,9 @@ m4_symbol_value_print (m4 *context, m4_symbol_value 
*value, m4_obstack *obs,
        result = true;
       break;
     case M4_SYMBOL_FUNC:
-      if (flatten)
-       {
-         if (quotes)
-           {
-             obstack_grow (obs, quotes->str1, quotes->len1);
-             obstack_grow (obs, quotes->str2, quotes->len2);
-           }
-         module = false;
-       }
-      else
-       {
-         bp = m4_get_symbol_value_builtin (value);
-         obstack_1grow (obs, '<');
-         obstack_grow (obs, bp->name, strlen (bp->name));
-         obstack_1grow (obs, '>');
-       }
+      m4_builtin_print (obs, m4_get_symbol_value_builtin (value), flatten,
+                       quotes, module ? VALUE_MODULE (value) : NULL);
+      module = false;
       break;
     case M4_SYMBOL_PLACEHOLDER:
       if (flatten)
@@ -608,6 +594,7 @@ m4_symbol_value_print (m4 *context, m4_symbol_value *value, 
m4_obstack *obs,
       break;
     case M4_SYMBOL_COMP:
       chain = value->u.u_c.chain;
+      assert (!module);
       if (quotes)
        obstack_grow (obs, quotes->str1, quotes->len1);
       while (chain && !result)
@@ -619,6 +606,11 @@ m4_symbol_value_print (m4 *context, m4_symbol_value 
*value, m4_obstack *obs,
                                           chain->u.u_s.len, NULL, &len))
                result = true;
              break;
+           case M4__CHAIN_FUNC:
+             m4_builtin_print (obs, &chain->u.builtin->builtin, flatten,
+                               quotes,
+                               module ? chain->u.builtin->module : NULL);
+             break;
            case M4__CHAIN_ARGV:
              if (m4_arg_print (context, obs, chain->u.u_a.argv,
                                chain->u.u_a.index,
@@ -637,7 +629,6 @@ m4_symbol_value_print (m4 *context, m4_symbol_value *value, 
m4_obstack *obs,
          }
       if (quotes)
        obstack_grow (obs, quotes->str2, quotes->len2);
-      assert (!module);
       break;
     default:
       assert (!"m4_symbol_value_print");
diff --git a/m4/utility.c b/m4/utility.c
index 0a3296b..1e17d61 100644
--- a/m4/utility.c
+++ b/m4/utility.c
@@ -119,6 +119,27 @@ m4_parse_truth_arg (m4 *context, const char *arg, const 
char *me,
   return previous;
 }
 
+/* Helper method to look up a symbol table entry given an argument.
+   Warn on behalf of CALLER if VALUE is not a text argument, or if
+   MUST_EXIST and no macro exists by the name in VALUE.  Return the
+   result of the lookup, or NULL.  */
+m4_symbol *
+m4_symbol_value_lookup (m4 *context, const char *caller,
+                       m4_symbol_value *value, bool must_exist)
+{
+  m4_symbol *result = NULL;
+  if (m4_is_symbol_value_text (value))
+    {
+      const char *name = m4_get_symbol_value_text (value);
+      result = m4_symbol_lookup (M4SYMTAB, name);
+      if (must_exist && !result)
+       m4_warn (context, 0, caller, _("undefined macro `%s'"), name);
+    }
+  else
+    m4_warn (context, 0, caller, _("invalid macro name ignored"));
+  return result;
+}
+
 /* Helper for all error reporting.  Report message based on FORMAT and
    ARGS, on behalf of MACRO, at the optional location FILE and LINE.
    If ERRNUM, decode the errno value as part of the message.  If
diff --git a/modules/gnu.c b/modules/gnu.c
index d34cb4b..5309968 100644
--- a/modules/gnu.c
+++ b/modules/gnu.c
@@ -56,7 +56,7 @@
   BUILTIN (patsubst,   false,  true,   true,   2,      4  )    \
   BUILTIN (regexp,     false,  true,   true,   2,      4  )    \
   BUILTIN (renamesyms, false,  true,   false,  2,      3  )    \
-  BUILTIN (m4symbols,  false,  false,  false,  0,      -1 )    \
+  BUILTIN (m4symbols,  true,   false,  false,  0,      -1 )    \
   BUILTIN (syncoutput, false,  true,   false,  1,      1  )    \
 
 
diff --git a/modules/m4.c b/modules/m4.c
index ca37bef..359839b 100644
--- a/modules/m4.c
+++ b/modules/m4.c
@@ -64,15 +64,15 @@ extern void m4_make_temp     (m4 *context, m4_obstack *obs, 
const char *macro,
   BUILTIN (changequote,        false,  false,  false,  0,      2  )    \
   BUILTIN (decr,       false,  true,   true,   1,      1  )    \
   BUILTIN (define,     true,   true,   false,  1,      2  )    \
-  BUILTIN (defn,       false,  true,   false,  1,      -1 )    \
+  BUILTIN (defn,       true,   true,   false,  1,      -1 )    \
   BUILTIN (divert,     false,  false,  false,  0,      2  )    \
   BUILTIN (divnum,     false,  false,  false,  0,      0  )    \
   BUILTIN (dnl,                false,  false,  false,  0,      0  )    \
-  BUILTIN (dumpdef,    false,  false,  false,  0,      -1 )    \
+  BUILTIN (dumpdef,    true,   false,  false,  0,      -1 )    \
   BUILTIN (errprint,   false,  true,   false,  1,      -1 )    \
   BUILTIN (eval,       false,  true,   true,   1,      3  )    \
-  BUILTIN (ifdef,      false,  true,   false,  2,      3  )    \
-  BUILTIN (ifelse,     false,  true,   false,  1,      -1 )    \
+  BUILTIN (ifdef,      true,   true,   false,  2,      3  )    \
+  BUILTIN (ifelse,     true,   true,   false,  1,      -1 )    \
   BUILTIN (include,    false,  true,   false,  1,      1  )    \
   BUILTIN (incr,       false,  true,   true,   1,      1  )    \
   BUILTIN (index,      false,  true,   true,   2,      2  )    \
@@ -81,17 +81,17 @@ extern void m4_make_temp     (m4 *context, m4_obstack *obs, 
const char *macro,
   BUILTIN (m4wrap,     false,  true,   false,  1,      -1 )    \
   BUILTIN (maketemp,   false,  true,   false,  1,      1  )    \
   BUILTIN (mkstemp,    false,  true,   false,  1,      1  )    \
-  BUILTIN (popdef,     false,  true,   false,  1,      -1 )    \
+  BUILTIN (popdef,     true,   true,   false,  1,      -1 )    \
   BUILTIN (pushdef,    true,   true,   false,  1,      2  )    \
-  BUILTIN (shift,      false,  true,   false,  1,      -1 )    \
+  BUILTIN (shift,      true,   true,   false,  1,      -1 )    \
   BUILTIN (sinclude,   false,  true,   false,  1,      1  )    \
   BUILTIN (substr,     false,  true,   true,   2,      3  )    \
   BUILTIN (syscmd,     false,  true,   true,   1,      1  )    \
   BUILTIN (sysval,     false,  false,  false,  0,      0  )    \
-  BUILTIN (traceoff,   false,  false,  false,  0,      -1 )    \
-  BUILTIN (traceon,    false,  false,  false,  0,      -1 )    \
+  BUILTIN (traceoff,   true,   false,  false,  0,      -1 )    \
+  BUILTIN (traceon,    true,   false,  false,  0,      -1 )    \
   BUILTIN (translit,   false,  true,   true,   2,      3  )    \
-  BUILTIN (undefine,   false,  true,   false,  1,      -1 )    \
+  BUILTIN (undefine,   true,   true,   false,  1,      -1 )    \
   BUILTIN (undivert,   false,  false,  false,  0,      -1 )    \
 
 
@@ -170,12 +170,9 @@ M4BUILTIN_HANDLER (undefine)
   size_t i;
   for (i = 1; i < argc; i++)
     {
-      const char *name = M4ARG (i);
-
-      if (!m4_symbol_lookup (M4SYMTAB, name))
-       m4_warn (context, 0, me, _("undefined macro `%s'"), name);
-      else
-       m4_symbol_delete (M4SYMTAB, name);
+      m4_symbol_value *value = m4_arg_symbol (argv, i);
+      if (m4_symbol_value_lookup (context, me, value, true))
+       m4_symbol_delete (M4SYMTAB, m4_get_symbol_value_text (value));
     }
 }
 
@@ -198,17 +195,13 @@ M4BUILTIN_HANDLER (popdef)
   size_t i;
   for (i = 1; i < argc; i++)
     {
-      const char *name = M4ARG (i);
-
-      if (!m4_symbol_lookup (M4SYMTAB, name))
-       m4_warn (context, 0, me, _("undefined macro `%s'"), name);
-      else
-       m4_symbol_popdef (M4SYMTAB, name);
+      m4_symbol_value *value = m4_arg_symbol (argv, i);
+      if (m4_symbol_value_lookup (context, me, value, true))
+       m4_symbol_popdef (M4SYMTAB, m4_get_symbol_value_text (value));
     }
 }
 
 
-
 
 /* --- CONDITIONALS OF M4 --- */
 
@@ -216,7 +209,9 @@ M4BUILTIN_HANDLER (popdef)
 M4BUILTIN_HANDLER (ifdef)
 {
   m4_push_arg (context, obs, argv,
-              m4_symbol_lookup (M4SYMTAB, M4ARG (1)) ? 2 : 3);
+              (m4_symbol_value_lookup (context, M4ARG (0),
+                                       m4_arg_symbol (argv, 1), false)
+               ? 2 : 3));
 }
 
 M4BUILTIN_HANDLER (ifelse)
@@ -313,12 +308,11 @@ m4_dump_symbols (m4 *context, m4_dump_symbol_data *data, 
size_t argc,
 
       for (i = 1; i < argc; i++)
        {
-         const char *name = M4ARG (i);
-         symbol = m4_symbol_lookup (M4SYMTAB, name);
-         if (symbol != NULL)
-           dump_symbol_CB (NULL, name, symbol, data);
-         else if (complain)
-           m4_warn (context, 0, me, _("undefined macro `%s'"), name);
+         m4_symbol_value *value = m4_arg_symbol (argv, i);
+         symbol = m4_symbol_value_lookup (context, me, value, complain);
+         if (symbol)
+           dump_symbol_CB (NULL, m4_get_symbol_value_text (value), symbol,
+                           data);
        }
     }
 
@@ -371,18 +365,18 @@ M4BUILTIN_HANDLER (defn)
 
   for (i = 1; i < argc; i++)
     {
-      const char *name = M4ARG (i);
-      m4_symbol *symbol = m4_symbol_lookup (M4SYMTAB, name);
+      m4_symbol_value *value = m4_arg_symbol (argv, i);
+      m4_symbol *symbol = m4_symbol_value_lookup (context, me, value, true);
 
       if (!symbol)
-       m4_warn (context, 0, me, _("undefined macro `%s'"), name);
+       ;
       else if (m4_is_symbol_text (symbol))
        m4_shipout_string (context, obs, m4_get_symbol_text (symbol),
                           m4_get_symbol_len (symbol), true);
       else if (m4_is_symbol_func (symbol))
        m4_push_builtin (context, m4_get_symbol_value (symbol));
       else if (m4_is_symbol_placeholder (symbol))
-       m4_warn (context, 0, name,
+       m4_warn (context, 0, M4ARG (i),
                 _("builtin `%s' requested by frozen file not found"),
                 m4_get_symbol_placeholder (symbol));
       else
@@ -613,8 +607,8 @@ M4BUILTIN_HANDLER (dnl)
   m4_skip_line (context, M4ARG (0));
 }
 
-/* Shift all argument one to the left, discarding the first argument.  Each
-   output argument is quoted with the current quotes.  */
+/* Shift all arguments one to the left, discarding the first argument.
+   Each output argument is quoted with the current quotes.  */
 M4BUILTIN_HANDLER (shift)
 {
   m4_push_args (context, obs, argv, true, true);
@@ -854,6 +848,7 @@ M4BUILTIN_HANDLER (m4wrap)
 
 M4BUILTIN_HANDLER (traceon)
 {
+  const char *me = M4ARG (0);
   size_t i;
 
   if (argc == 1)
@@ -861,12 +856,16 @@ M4BUILTIN_HANDLER (traceon)
                                      | M4_DEBUG_TRACE_ALL));
   else
     for (i = 1; i < argc; i++)
-      m4_set_symbol_name_traced (M4SYMTAB, M4ARG (i), true);
+      if (m4_is_arg_text (argv, i))
+       m4_set_symbol_name_traced (M4SYMTAB, M4ARG (i), true);
+      else
+       m4_warn (context, 0, me, _("invalid macro name ignored"));
 }
 
 /* Disable tracing of all specified macros, or all, if none is specified.  */
 M4BUILTIN_HANDLER (traceoff)
 {
+  const char *me = M4ARG (0);
   size_t i;
 
   if (argc == 1)
@@ -874,7 +873,10 @@ M4BUILTIN_HANDLER (traceoff)
                                      & ~M4_DEBUG_TRACE_ALL));
   else
     for (i = 1; i < argc; i++)
-      m4_set_symbol_name_traced (M4SYMTAB, M4ARG (i), false);
+      if (m4_is_arg_text (argv, i))
+       m4_set_symbol_name_traced (M4SYMTAB, M4ARG (i), false);
+      else
+       m4_warn (context, 0, me, _("invalid macro name ignored"));
 }
 
 
-- 
1.5.4

>From d53cf5ec91d8991f633233ed3bd72384b7cbd8b5 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Wed, 21 Nov 2007 10:14:28 -0700
Subject: [PATCH] Stage 19: allow builtin tokens in more macros.

* src/m4.h (enum token_chain_type): Add CHAIN_FUNC.
(struct token_chain): Add func member.
* src/input.c (push_token, pop_input, input_print, peek_input)
(next_char_1, init_macro_token): Handle builtin tokens from
back-references.
(next_token): Flatten builtin tokens inside comments or quotes,
except when a builtin is the only thing inside quotes.
* src/macro.c (expand_argument): Strengthen assertion.
(collect_arguments): Handle builtin tokens.
(expand_macro): Fix harmless typo.
(arg_token): Add parameter.
(arg_type, arg_text, arg_equal, arg_empty, arg_len, arg_func)
(arg_print, push_arg_quote, push_args): Update callers to either
require flattened arguments or to handle builtins.
* src/builtin.c (m4_defn, m4_dumpdef, m4_ifdef, m4_ifelse)
(m4_popdef, m4_shift, m4_traceoff, m4_traceon, m4_undefine):
Handle builtin tokens, either by recognizing invalid macro names
or passing them through transparently.
(define_user_macro): Make all user macros handle builtin token
arguments transparently.
* doc/m4.texinfo (Defn, Ifdef, Ifelse, Debuglen): Document and
test the new behavior.
* NEWS: Document this change.

(cherry picked from commit 434656c96d6486cf959c3050aa85aecb72d948a0)

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog      |   33 ++++++++++++
 NEWS           |    8 +++
 doc/m4.texinfo |  128 +++++++++++++++++++++++++++++++++++++++++-------
 src/builtin.c  |   74 +++++++++++++++++++++-------
 src/input.c    |  148 ++++++++++++++++++++++++++++++++++++++++++++++----------
 src/m4.h       |    3 +-
 src/macro.c    |  125 ++++++++++++++++++++++++++++++++---------------
 7 files changed, 414 insertions(+), 105 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 9f25499..bce309d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,36 @@
+2008-03-14  Eric Blake  <address@hidden>
+
+       Stage 19: allow builtin tokens in more macros.
+       Allow builtin tokens inside symbol chains, although for now, they
+       are not allowed inside comments or quotes.  Enable builtin token
+       handling in more macros, if only to consistently diagnose invalid
+       macro names.
+       Memory impact: none.
+       Speed impact: slight impact, due to more bookkeeping.
+       * src/m4.h (enum token_chain_type): Add CHAIN_FUNC.
+       (struct token_chain): Add func member.
+       * src/input.c (push_token, pop_input, input_print, peek_input)
+       (next_char_1, init_macro_token): Handle builtin tokens from
+       back-references.
+       (next_token): Flatten builtin tokens inside comments or quotes,
+       except when a builtin is the only thing inside quotes.
+       * src/macro.c (expand_argument): Strengthen assertion.
+       (collect_arguments): Handle builtin tokens.
+       (expand_macro): Fix harmless typo.
+       (arg_token): Add parameter.
+       (arg_type, arg_text, arg_equal, arg_empty, arg_len, arg_func)
+       (arg_print, push_arg_quote, push_args): Update callers to either
+       require flattened arguments or to handle builtins.
+       * src/builtin.c (m4_defn, m4_dumpdef, m4_ifdef, m4_ifelse)
+       (m4_popdef, m4_shift, m4_traceoff, m4_traceon, m4_undefine):
+       Handle builtin tokens, either by recognizing invalid macro names
+       or passing them through transparently.
+       (define_user_macro): Make all user macros handle builtin token
+       arguments transparently.
+       * doc/m4.texinfo (Defn, Ifdef, Ifelse, Debuglen): Document and
+       test the new behavior.
+       * NEWS: Document this change.
+
 2008-03-13  Eric Blake  <address@hidden>
 
        Consistently cast malloc results, for C++ compilation.
diff --git a/NEWS b/NEWS
index 7a88383..7dc3aba 100644
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,14 @@ Foundation, Inc.
 ** Fix regression introduced in 1.4.10b where using `builtin' or `indir'
    to perform nested `shift' calls triggered an assertion failure.
 
+** Enhance the `ifdef', `ifelse', and `shift' builtins, as well as all
+   user macros, to transparently handle builtin tokens generated by `defn'.
+
+** Enhance the `defn', `dumpdef', `ifdef', `popdef', `traceon', `traceoff',
+   and `undefine' macros to warn when encountering a builtin token in the
+   context of a macro name, rather than acting on the empty string.  This
+   was already done for `define', `pushdef', `builtin', and `indir'.
+
 * Noteworthy changes in Version 1.4.10b (2008-02-25) [beta]
   Released by Eric Blake, based on git version 1.4.10a
 
diff --git a/doc/m4.texinfo b/doc/m4.texinfo
index 7b249bd..7ac9867 100644
--- a/doc/m4.texinfo
+++ b/doc/m4.texinfo
@@ -2214,11 +2214,17 @@ defn([l], [r])
 
 @cindex builtins, special tokens
 @cindex tokens, builtin macro
-Using @code{defn} to generate special tokens for builtin macros outside
-of expected contexts can sometimes trigger warnings.  But most of the
-time, such tokens are silently converted to the empty string.
+Using @code{defn} to generate special tokens for builtin macros will
+generate a warning in contexts where a macro name is expected.  But in
+contexts that operate on text, the builtin token is just silently
+converted to an empty string.  As of M4 1.4.11, expansion of user macros
+will also preserve builtin tokens.  However, any use of builtin tokens
+outside of the second argument to @code{define} and @code{pushdef} is
+generally not portable, since earlier @acronym{GNU} M4 versions, as well
+as other @code{m4} implementations, vary on how such tokens are treated.
 
 @example
+$ @kbd{m4 -d}
 defn(`defn')
 @result{}
 define(defn(`divnum'), `cannot redefine a builtin token')
@@ -2226,12 +2232,50 @@ define(defn(`divnum'), `cannot redefine a builtin 
token')
 @result{}
 divnum
 @result{}0
+len(defn(`divnum'))
address@hidden
 define(`echo', `$@@')
 @result{}
-define(`mydivnum', echo(defn(`divnum')))
+define(`mydivnum', shift(echo(`', defn(`divnum'))))
 @result{}
 mydivnum
address@hidden
+define(`', `empty-$1')
address@hidden
+defn(defn(`divnum'))
address@hidden:stdin:9: Warning: defn: invalid macro name ignored
address@hidden
+pushdef(defn(`divnum'), `oops')
address@hidden:stdin:10: Warning: pushdef: invalid macro name ignored
address@hidden
+traceon(defn(`divnum'))
address@hidden:stdin:11: Warning: traceon: invalid macro name ignored
address@hidden
+indir(defn(`divnum'), `string')
address@hidden:stdin:12: Warning: indir: invalid macro name ignored
address@hidden
+indir(`', `string')
address@hidden
+traceoff(defn(`divnum'))
address@hidden:stdin:14: Warning: traceoff: invalid macro name ignored
address@hidden
+popdef(defn(`divnum'))
address@hidden:stdin:15: Warning: popdef: invalid macro name ignored
address@hidden
+dumpdef(defn(`divnum'))
address@hidden:stdin:16: Warning: dumpdef: invalid macro name ignored
address@hidden
+undefine(defn(`divnum'))
address@hidden:stdin:17: Warning: undefine: invalid macro name ignored
 @result{}
+dumpdef(`')
address@hidden:@tabchar{}`empty-$1'
address@hidden
+define(`foo', `define(`$1', $2)')dnl
+foo(`bar', defn(`divnum'))
address@hidden
+bar
address@hidden
 @end example
 
 Also note that @code{defn} with multiple arguments can only join text
@@ -2606,6 +2650,22 @@ ifdef(`no_such_macro', `yes', `no', `extra argument')
 @result{}no
 @end example
 
+As of M4 1.4.11, @code{ifdef} transparently handles builtin tokens
+generated by @code{defn} (@pxref{Defn}) that occur in either
address@hidden, although a warning is issued for invalid macro names.
+
address@hidden
+define(`', `empty')
address@hidden
+ifdef(defn(`defn'), `yes', `no')
address@hidden:stdin:2: Warning: ifdef: invalid macro name ignored
address@hidden
+define(`foo', ifdef(`divnum', defn(`divnum'), `undefined'))
address@hidden
+foo
address@hidden
address@hidden example
+
 @node Ifelse
 @section If-else construct, or multibranch
 
@@ -2706,6 +2766,24 @@ ifelse(`foo', `bar', `3', `gnu', `gnats', `6', `7', `8')
 @result{}7
 @end example
 
+As of M4 1.4.11, @code{ifelse} transparently handles builtin tokens
+generated by @code{defn} (@pxref{Defn}).  Because of this, it is always
+safe to compare two macro definitions, without worrying whether the
+macro might be a builtin.
+
address@hidden
+ifelse(defn(`defn'), `', `yes', `no')
address@hidden
+ifelse(defn(`defn'), defn(`divnum'), `yes', `no')
address@hidden
+ifelse(defn(`defn'), defn(`defn'), `yes', `no')
address@hidden
+define(`foo', ifelse(`', `', defn(`divnum')))
address@hidden
+foo
address@hidden
address@hidden example
+
 @ignore
 @comment Stress tests, not worth documenting.
 
@@ -2764,8 +2842,8 @@ ifelse(`-01234567890123456789', `-'e(long)`-', `yes', 
`no')
 @result{}no
 @end example
 
address@hidden It would be nice to pass builtin tokens through ifelse, m4wrap,
address@hidden user macros; hence the fixmes.
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.
 @example
 define(`e', `$@@')define(`q', ``$@@'')define(`u', `$*')
 @result{}
@@ -2775,28 +2853,40 @@ cmp(`defn(`defn')', `defn(`d')')
 @result{}yes
 cmp(`defn(`defn')', ``<defn>'')
 @result{}no
-cmp(`q(defn(`defn'))', `q(defn(`d'))')
address@hidden
-cmp(`q(defn(`defn'))', `q(`<defn>')')
address@hidden
-cmp(`q(defn(`defn'))', ``'')
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(defn(`defn'))', `q(defn(`d'))')-fixme
address@hidden:stdin:5: Warning: ifelse: cannot quote builtin
address@hidden:stdin:5: Warning: ifelse: cannot quote builtin
address@hidden
+cmp(`q(defn(`defn'))', `q(`<defn>')')-fixme
address@hidden:stdin:6: Warning: ifelse: cannot quote builtin
address@hidden
+cmp(`q(defn(`defn'))', ``'')-fixme
address@hidden:stdin:7: Warning: ifelse: cannot quote builtin
address@hidden
+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
 @result{}yes-fixme
 define(`cat', `$1`'ifelse(`$#', `1', `', `$0(shift($@@))')')
 @result{}
 cat(`define(`foo',', defn(`divnum'), `)foo')-fixme
address@hidden:stdin:13: Warning: ifelse: cannot quote builtin
 @result{}-fixme
 cat(e(`define(`bar',', defn(`divnum'), `)bar'))-fixme
address@hidden:stdin:14: Warning: ifelse: cannot quote builtin
 @result{}-fixme
 m4wrap(`u('q(`cat(`define(`baz','', defn(`divnum'), ``)baz')')`)-fixme
 ')
address@hidden:stdin:15: Warning: m4wrap: cannot quote builtin
 @result{}
 ^D
 @result{}-fixme
@@ -3592,7 +3682,7 @@ echo(`1', `long string')
 @result{}1,long string
 indir(`echo', defn(`changequote'))
 @error{}m4trace: -2- defn(`change...')
address@hidden: -1- indir(`echo', <changequote>) -> ``''
address@hidden: -1- indir(`echo', <changequote>) -> ``<changequote>''
 @result{}
 @end example
 
diff --git a/src/builtin.c b/src/builtin.c
index a1d4d01..a441c4c 100644
--- a/src/builtin.c
+++ b/src/builtin.c
@@ -106,17 +106,17 @@ builtin_tab[] =
   { "debugfile",       true,   false,  false,  m4_debugfile },
   { "decr",            false,  false,  true,   m4_decr },
   { "define",          false,  true,   true,   m4_define },
-  { "defn",            false,  false,  true,   m4_defn },
+  { "defn",            false,  true,   true,   m4_defn },
   { "divert",          false,  false,  false,  m4_divert },
   { "divnum",          false,  false,  false,  m4_divnum },
   { "dnl",             false,  false,  false,  m4_dnl },
-  { "dumpdef",         false,  false,  false,  m4_dumpdef },
+  { "dumpdef",         false,  true,   false,  m4_dumpdef },
   { "errprint",                false,  false,  true,   m4_errprint },
   { "esyscmd",         true,   false,  true,   m4_esyscmd },
   { "eval",            false,  false,  true,   m4_eval },
   { "format",          true,   false,  true,   m4_format },
-  { "ifdef",           false,  false,  true,   m4_ifdef },
-  { "ifelse",          false,  false,  true,   m4_ifelse },
+  { "ifdef",           false,  true,   true,   m4_ifdef },
+  { "ifelse",          false,  true,   true,   m4_ifelse },
   { "include",         false,  false,  true,   m4_include },
   { "incr",            false,  false,  true,   m4_incr },
   { "index",           false,  false,  true,   m4_index },
@@ -127,18 +127,18 @@ builtin_tab[] =
   { "maketemp",                false,  false,  true,   m4_maketemp },
   { "mkstemp",         false,  false,  true,   m4_mkstemp },
   { "patsubst",                true,   false,  true,   m4_patsubst },
-  { "popdef",          false,  false,  true,   m4_popdef },
+  { "popdef",          false,  true,   true,   m4_popdef },
   { "pushdef",         false,  true,   true,   m4_pushdef },
   { "regexp",          true,   false,  true,   m4_regexp },
-  { "shift",           false,  false,  true,   m4_shift },
+  { "shift",           false,  true,   true,   m4_shift },
   { "sinclude",                false,  false,  true,   m4_sinclude },
   { "substr",          false,  false,  true,   m4_substr },
   { "syscmd",          false,  false,  true,   m4_syscmd },
   { "sysval",          false,  false,  false,  m4_sysval },
-  { "traceoff",                false,  false,  false,  m4_traceoff },
-  { "traceon",         false,  false,  false,  m4_traceon },
+  { "traceoff",                false,  true,   false,  m4_traceoff },
+  { "traceon",         false,  true,   false,  m4_traceon },
   { "translit",                false,  false,  true,   m4_translit },
-  { "undefine",                false,  false,  true,   m4_undefine },
+  { "undefine",                false,  true,   true,   m4_undefine },
   { "undivert",                false,  false,  false,  m4_undivert },
 
   { 0,                 false,  false,  false,  0 },
@@ -440,6 +440,7 @@ define_user_macro (const char *name, size_t name_len, const 
char *text,
 
   SYMBOL_TYPE (s) = TOKEN_TEXT;
   SYMBOL_TEXT (s) = defn;
+  SYMBOL_MACRO_ARGS (s) = true;
 
   /* Implement --warn-macro-sequence.  */
   if (macro_sequence_inuse && text)
@@ -693,11 +694,15 @@ m4_define (struct obstack *obs, int argc, macro_arguments 
*argv)
 static void
 m4_undefine (struct obstack *obs, int argc, macro_arguments *argv)
 {
+  const char *me = ARG (0);
   int i;
-  if (bad_argc (ARG (0), argc, 1, -1))
+  if (bad_argc (me, argc, 1, -1))
     return;
   for (i = 1; i < argc; i++)
-    lookup_symbol (ARG (i), SYMBOL_DELETE);
+    if (arg_type (argv, i) != TOKEN_TEXT)
+      m4_warn (0, me, _("invalid macro name ignored"));
+    else
+      lookup_symbol (ARG (i), SYMBOL_DELETE);
 }
 
 static void
@@ -709,11 +714,15 @@ m4_pushdef (struct obstack *obs, int argc, 
macro_arguments *argv)
 static void
 m4_popdef (struct obstack *obs, int argc, macro_arguments *argv)
 {
+  const char *me = ARG (0);
   int i;
-  if (bad_argc (ARG (0), argc, 1, -1))
+  if (bad_argc (me, argc, 1, -1))
     return;
   for (i = 1; i < argc; i++)
-    lookup_symbol (ARG (i), SYMBOL_POPDEF);
+    if (arg_type (argv, i) != TOKEN_TEXT)
+      m4_warn (0, me, _("invalid macro name ignored"));
+    else
+      lookup_symbol (ARG (i), SYMBOL_POPDEF);
 }
 
 /*---------------------.
@@ -723,10 +732,17 @@ m4_popdef (struct obstack *obs, int argc, macro_arguments 
*argv)
 static void
 m4_ifdef (struct obstack *obs, int argc, macro_arguments *argv)
 {
+  const char *me = ARG (0);
   symbol *s;
 
-  if (bad_argc (ARG (0), argc, 2, 3))
+  if (bad_argc (me, argc, 2, 3))
     return;
+  if (arg_type (argv, 1) != TOKEN_TEXT)
+    {
+      m4_warn (0, me, _("invalid macro name ignored"));
+      push_arg (obs, argv, 3);
+      return;
+    }
   s = lookup_symbol (ARG (1), SYMBOL_LOOKUP);
   push_arg (obs, argv, (s && SYMBOL_TYPE (s) != TOKEN_VOID) ? 2 : 3);
 }
@@ -834,6 +850,11 @@ m4_dumpdef (struct obstack *obs, int argc, macro_arguments 
*argv)
     {
       for (i = 1; i < argc; i++)
        {
+         if (arg_type (argv, i) != TOKEN_TEXT)
+           {
+             m4_warn (0, me, _("invalid macro name ignored"));
+             continue;
+           }
          s = lookup_symbol (ARG (i), SYMBOL_LOOKUP);
          if (s != NULL && SYMBOL_TYPE (s) != TOKEN_VOID)
            dump_symbol (s, &data);
@@ -968,6 +989,11 @@ m4_defn (struct obstack *obs, int argc, macro_arguments 
*argv)
 
   for (i = 1; i < argc; i++)
     {
+      if (arg_type (argv, i) != TOKEN_TEXT)
+       {
+         m4_warn (0, me, _("invalid macro name ignored"));
+         continue;
+       }
       s = lookup_symbol (ARG (i), SYMBOL_LOOKUP);
       if (s == NULL)
        continue;
@@ -1289,10 +1315,10 @@ m4_dnl (struct obstack *obs, int argc, macro_arguments 
*argv)
   skip_line (me);
 }
 
-/*-------------------------------------------------------------------------.
-| Shift all argument one to the left, discarding the first argument.  Each |
-| output argument is quoted with the current quotes.                      |
-`-------------------------------------------------------------------------*/
+/*--------------------------------------------------------------------.
+| Shift all arguments one to the left, discarding the first          |
+| argument.  Each output argument is quoted with the current quotes.  |
+`--------------------------------------------------------------------*/
 
 static void
 m4_shift (struct obstack *obs, int argc, macro_arguments *argv)
@@ -1624,6 +1650,7 @@ set_trace (symbol *sym, void *data)
 static void
 m4_traceon (struct obstack *obs, int argc, macro_arguments *argv)
 {
+  const char *me = ARG (0);
   symbol *s;
   int i;
 
@@ -1632,6 +1659,11 @@ m4_traceon (struct obstack *obs, int argc, 
macro_arguments *argv)
   else
     for (i = 1; i < argc; i++)
       {
+       if (arg_type (argv, i) != TOKEN_TEXT)
+         {
+           m4_warn (0, me, _("invalid macro name ignored"));
+           continue;
+         }
        s = lookup_symbol (ARG (i), SYMBOL_INSERT);
        set_trace (s, obs);
       }
@@ -1644,6 +1676,7 @@ m4_traceon (struct obstack *obs, int argc, 
macro_arguments *argv)
 static void
 m4_traceoff (struct obstack *obs, int argc, macro_arguments *argv)
 {
+  const char *me = ARG (0);
   symbol *s;
   int i;
 
@@ -1652,6 +1685,11 @@ m4_traceoff (struct obstack *obs, int argc, 
macro_arguments *argv)
   else
     for (i = 1; i < argc; i++)
       {
+       if (arg_type (argv, i) != TOKEN_TEXT)
+         {
+           m4_warn (0, me, _("invalid macro name ignored"));
+           continue;
+         }
        s = lookup_symbol (ARG (i), SYMBOL_LOOKUP);
        if (s != NULL)
          set_trace (s, NULL);
diff --git a/src/input.c b/src/input.c
index 9d8b8f3..b8784d0 100644
--- a/src/input.c
+++ b/src/input.c
@@ -361,7 +361,7 @@ push_token (token_data *token, int level, bool inuse)
          return false;
        }
     }
-  else
+  else if (TOKEN_DATA_TYPE (token) != TOKEN_FUNC)
     {
       /* For composite tokens, if argv is already in use, creating
         additional references for long text segments is more
@@ -407,8 +407,23 @@ 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 (level == -1)
        {
          /* Nothing to copy, since link already lives on obstack.  */
@@ -568,7 +583,8 @@ pop_input (bool cleanup)
       break;
 
     case INPUT_MACRO:
-      if (!cleanup)
+      assert (!isp->u.func || !cleanup);
+      if (isp->u.func)
        return false;
       break;
 
@@ -585,6 +601,10 @@ pop_input (bool cleanup)
              if (chain->u.u_s.level >= 0)
                adjust_refcount (chain->u.u_s.level, false);
              break;
+           case CHAIN_FUNC:
+              if (chain->u.func)
+                return false;
+              break;
            case CHAIN_ARGV:
              if (chain->u.u_a.index < arg_argc (chain->u.u_a.argv))
                return false;
@@ -708,6 +728,10 @@ input_print (struct obstack *obs, const input_block *input)
                                        chain->u.u_s.len, &maxlen))
                return;
              break;
+           case CHAIN_FUNC:
+             func_print (obs, find_builtin_by_addr (chain->u.func), false,
+                         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,
@@ -770,7 +794,9 @@ peek_input (bool allow_argv)
          break;
 
        case INPUT_MACRO:
-         return CHAR_MACRO;
+         if (block->u.func)
+           return CHAR_MACRO;
+         break;
 
        case INPUT_CHAIN:
          chain = block->u.u_c.chain;
@@ -783,6 +809,10 @@ peek_input (bool allow_argv)
                  if (chain->u.u_s.len)
                    return to_uchar (*chain->u.u_s.str);
                  break;
+               case CHAIN_FUNC:
+                 if (chain->u.func)
+                   return CHAR_MACRO;
+                 break;
                case CHAIN_ARGV:
                  argc = arg_argc (chain->u.u_a.argv);
                  if (chain->u.u_a.index == argc)
@@ -891,9 +921,9 @@ next_char_1 (bool allow_quote)
          break;
 
        case INPUT_MACRO:
-         /* INPUT_MACRO input sources has only one token */
-         pop_input (true);
-         return CHAR_MACRO;
+         if (isp->u.func)
+           return CHAR_MACRO;
+         break;
 
        case INPUT_CHAIN:
          chain = isp->u.u_c.chain;
@@ -914,6 +944,10 @@ next_char_1 (bool allow_quote)
                  if (chain->u.u_s.level >= 0)
                    adjust_refcount (chain->u.u_s.level, false);
                  break;
+               case CHAIN_FUNC:
+                 if (chain->u.func)
+                   return CHAR_MACRO;
+                 break;
                case CHAIN_ARGV:
                  if (chain->u.u_a.index == arg_argc (chain->u.u_a.argv))
                    {
@@ -984,17 +1018,37 @@ skip_line (const char *name)
     input_change = true;
 }
 
-/*-------------------------------------------------------------------.
-| When a MACRO token is seen, next_token () uses init_macro_token () |
-| to retrieve the value of the function pointer and store it in TD.  |
-`-------------------------------------------------------------------*/
+/*------------------------------------------------------------------.
+| When next_token() sees a builtin token with peek_input, this     |
+| retrieves the value of the function pointer, stores it in TD, and |
+| consumes the input so the caller does not need to do next_char.   |
+| If TD is NULL, discard the token instead.                        |
+`------------------------------------------------------------------*/
 
 static void
 init_macro_token (token_data *td)
 {
-  assert (isp->type == INPUT_MACRO);
-  TOKEN_DATA_TYPE (td) = TOKEN_FUNC;
-  TOKEN_DATA_FUNC (td) = isp->u.func;
+  int ch = next_char (false);
+  assert (ch == CHAR_MACRO);
+  if (td)
+    TOKEN_DATA_TYPE (td) = TOKEN_FUNC;
+  if (isp->type == INPUT_MACRO)
+    {
+      assert (isp->u.func);
+      if (td)
+       TOKEN_DATA_FUNC (td) = isp->u.func;
+      isp->u.func = NULL;
+    }
+  else
+    {
+      token_chain *chain;
+      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_FUNC (td) = chain->u.func;
+      chain->u.func = NULL;
+    }
 }
 
 /*-------------------------------------------------------------------.
@@ -1516,7 +1570,6 @@ next_token (token_data *td, int *line, struct obstack 
*obs, bool allow_argv,
   if (ch == CHAR_MACRO)
     {
       init_macro_token (td);
-      next_char (false);
 #ifdef DEBUG_INPUT
       xfprintf (stderr, "next_token -> MACDEF (%s)\n",
                find_builtin_by_addr (TOKEN_DATA_FUNC (td))->name);
@@ -1543,20 +1596,30 @@ next_token (token_data *td, int *line, struct obstack 
*obs, bool allow_argv,
       if (obs)
        obs_td = obs;
       obstack_grow (obs_td, curr_comm.str1, curr_comm.len1);
-      while ((ch = next_char (false)) < CHAR_EOF
-            && !MATCH (ch, curr_comm.str2, true))
-       obstack_1grow (obs_td, ch);
-      if (ch != CHAR_EOF)
+      while (1)
        {
+         ch = next_char (false);
+         if (ch == CHAR_EOF)
+           /* Current_file changed to "" if we see CHAR_EOF, use the
+              previous value we stored earlier.  */
+           m4_error_at_line (EXIT_FAILURE, 0, file, *line, caller,
+                             _("end of file in comment"));
+         if (ch == CHAR_MACRO)
+           {
+             /* TODO support concatenation of builtins.  */
+             m4_warn_at_line (0, file, *line, caller,
+                              _("cannot comment builtin"));
+             init_macro_token (NULL);
+             continue;
+           }
+         if (MATCH (ch, curr_comm.str2, true))
+           {
+             obstack_grow (obs_td, curr_comm.str2, curr_comm.len2);
+             break;
+           }
          assert (ch < CHAR_EOF);
-         obstack_grow (obs_td, curr_comm.str2, curr_comm.len2);
+         obstack_1grow (obs_td, ch);
        }
-      else
-       /* Current_file changed to "" if we see CHAR_EOF, use the
-          previous value we stored earlier.  */
-       m4_error_at_line (EXIT_FAILURE, 0, file, *line, caller,
-                         _("end of file in comment"));
-
       type = TOKEN_STRING;
     }
   else if (default_word_regexp && (isalpha (ch) || ch == '_'))
@@ -1608,6 +1671,7 @@ next_token (token_data *td, int *line, struct obstack 
*obs, bool allow_argv,
 
   else if (!MATCH (ch, curr_quote.str1, true))
     {
+      assert (ch < CHAR_EOF);
       switch (ch)
        {
        case '(':
@@ -1630,6 +1694,7 @@ next_token (token_data *td, int *line, struct obstack 
*obs, bool allow_argv,
       if (obs)
        obs_td = obs;
       quote_level = 1;
+      type = TOKEN_STRING;
       while (1)
        {
          ch = next_char (obs != NULL && current_quote_age);
@@ -1639,6 +1704,35 @@ next_token (token_data *td, int *line, struct obstack 
*obs, bool allow_argv,
            m4_error_at_line (EXIT_FAILURE, 0, file, *line, caller,
                              _("end of file in string"));
 
+         if (ch == CHAR_MACRO)
+           {
+             /* TODO support concatenation of builtins.  */
+             if (obstack_object_size (obs_td) == 0
+                 && TOKEN_DATA_TYPE (td) == TOKEN_VOID)
+               {
+                 assert (quote_level == 1);
+                 init_macro_token (td);
+                 ch = peek_input (false);
+                 if (MATCH (ch, curr_quote.str2, false))
+                   {
+#ifdef DEBUG_INPUT
+                     const builtin *bp
+                       = find_builtin_by_addr (TOKEN_DATA_FUNC (td));
+                     xfprintf (stderr, "next_token -> MACDEF (%s)\n",
+                               bp->name);
+#endif
+                     ch = next_char (false);
+                     MATCH (ch, curr_quote.str2, true);
+                     return TOKEN_MACDEF;
+                   }
+                 TOKEN_DATA_TYPE (td) = TOKEN_VOID;
+               }
+             else
+               init_macro_token (NULL);
+             m4_warn_at_line (0, file, *line, caller,
+                              _("cannot quote builtin"));
+             continue;
+           }
          if (ch == CHAR_QUOTE)
            append_quote_token (obs, td);
          else if (MATCH (ch, curr_quote.str2, true))
@@ -1658,7 +1752,6 @@ next_token (token_data *td, int *line, struct obstack 
*obs, bool allow_argv,
              obstack_1grow (obs_td, ch);
            }
        }
-      type = TOKEN_STRING;
     }
 
   if (TOKEN_DATA_TYPE (td) == TOKEN_VOID)
@@ -1707,6 +1800,9 @@ next_token (token_data *td, int *line, struct obstack 
*obs, bool allow_argv,
                xfprintf (stderr, "%s", chain->u.u_s.str);
                len += chain->u.u_s.len;
                break;
+             case CHAIN_FUNC:
+               xfprintf (stderr, "<func>");
+               break;
              case CHAIN_ARGV:
                xfprintf (stderr, "address@hidden");
                break;
diff --git a/src/m4.h b/src/m4.h
index 7e35acc..ef45359 100644
--- a/src/m4.h
+++ b/src/m4.h
@@ -282,7 +282,7 @@ enum token_data_type
 enum token_chain_type
 {
   CHAIN_STR,   /* Link contains a string, u.u_s is valid.  */
-  /* TODO add CHAIN_FUNC.  */
+  CHAIN_FUNC,  /* Builtin function definition, u.func is valid.  */
   CHAIN_ARGV   /* Link contains a $@ reference, u.u_a is valid.  */
 };
 
@@ -303,6 +303,7 @@ struct token_chain
          int level;            /* Expansion level of link content, or -1.  */
        }
       u_s;
+      builtin_func *func;              /* Builtin token from defn.  */
       struct
        {
          macro_arguments *argv;        /* Reference to earlier address@hidden  
*/
diff --git a/src/macro.c b/src/macro.c
index 6123f05..f794d86 100644
--- a/src/macro.c
+++ b/src/macro.c
@@ -470,6 +470,7 @@ expand_argument (struct obstack *obs, token_data *argp, 
const char *caller)
          assert (paren_level == 0 && TOKEN_DATA_TYPE (argp) == TOKEN_VOID
                  && obstack_object_size (obs) == 0
                  && td.u.u_c.chain == td.u.u_c.end
+                 && td.u.u_c.chain->quote_age == age
                  && td.u.u_c.chain->type == CHAIN_ARGV);
          TOKEN_DATA_TYPE (argp) = TOKEN_COMP;
          argp->u.u_c.chain = argp->u.u_c.end = td.u.u_c.chain;
@@ -581,7 +582,8 @@ collect_arguments (symbol *sym, struct obstack *arguments,
   argv->wrapper = args.wrapper;
   argv->has_ref = args.has_ref;
   argv->has_func = args.has_func;
-  if (args.quote_age != quote_age ())
+  /* TODO allow funcs without crippling quote age.  */
+  if (args.quote_age != quote_age () || args.has_func)
     argv->quote_age = 0;
   argv->arraylen = args.arraylen;
   return argv;
@@ -664,7 +666,7 @@ expand_macro (symbol *sym)
       stacks[level].args =
        (struct obstack *) xmalloc (sizeof *stacks[level].args);
       stacks[level].argv =
-       (struct obstack *) xmalloc (sizeof *stacks[level].args);
+       (struct obstack *) xmalloc (sizeof *stacks[level].argv);
       obstack_init (stacks[level].args);
       obstack_init (stacks[level].argv);
       stacks[level].args_base = obstack_finish (stacks[level].args);
@@ -816,9 +818,10 @@ arg_adjust_refcount (macro_arguments *argv, bool increase)
 /* Given ARGV, return the token_data that contains argument INDEX;
    INDEX must be > 0, < argv->argc.  If LEVEL is non-NULL, *LEVEL is
    set to the obstack level that contains the token (which is not
-   necessarily the level of ARGV).  */
+   necessarily the level of ARGV).  If FLATTEN, avoid returning a
+   builtin function.  */
 static token_data *
-arg_token (macro_arguments *argv, unsigned int index, int *level)
+arg_token (macro_arguments *argv, unsigned int index, int *level, bool flatten)
 {
   unsigned int i;
   token_data *token;
@@ -826,8 +829,14 @@ arg_token (macro_arguments *argv, unsigned int index, int 
*level)
   assert (index && index < argv->argc);
   if (level)
     *level = argv->level;
+  flatten |= argv->flatten;
   if (!argv->wrapper)
-    return argv->array[index - 1];
+    {
+      token = argv->array[index - 1];
+      if (flatten && TOKEN_DATA_TYPE (token) == TOKEN_FUNC)
+       token = &empty_token;
+      return token;
+    }
 
   /* Must cycle through all tokens, until we find index, since a ref
      may occupy multiple indices.  */
@@ -842,10 +851,8 @@ arg_token (macro_arguments *argv, unsigned int index, int 
*level)
                        - chain->u.u_a.skip_last))
            {
              token = arg_token (chain->u.u_a.argv,
-                                chain->u.u_a.index - 1 + index, level);
-             if (chain->u.u_a.flatten
-                 && TOKEN_DATA_TYPE (token) == TOKEN_FUNC)
-               token = &empty_token;
+                                chain->u.u_a.index - 1 + index, level,
+                                flatten || chain->u.u_a.flatten);
              break;
            }
          index -= (chain->u.u_a.argv->argc - chain->u.u_a.index
@@ -897,7 +904,7 @@ arg_type (macro_arguments *argv, unsigned int index)
 
   if (argv->flatten || !argv->has_func || index == 0 || index >= argv->argc)
     return TOKEN_TEXT;
-  token = arg_token (argv, index, NULL);
+  token = arg_token (argv, index, NULL, false);
   type = TOKEN_DATA_TYPE (token);
   if (type == TOKEN_COMP && !token->u.u_c.has_func)
     type = TOKEN_TEXT;
@@ -923,7 +930,7 @@ arg_text (macro_arguments *argv, unsigned int index)
     return argv->argv0;
   if (index >= argv->argc)
     return "";
-  token = arg_token (argv, index, NULL);
+  token = arg_token (argv, index, NULL, false);
   switch (TOKEN_DATA_TYPE (token))
     {
     case TOKEN_TEXT:
@@ -939,11 +946,16 @@ arg_text (macro_arguments *argv, unsigned int index)
            case CHAIN_STR:
              obstack_grow (obs, chain->u.u_s.str, chain->u.u_s.len);
              break;
+           case CHAIN_FUNC:
+             /* TODO concatenate builtins.  */
+             assert (!"implemented");
+             abort ();
            case CHAIN_ARGV:
              arg_print (obs, chain->u.u_a.argv, chain->u.u_a.index,
                         quote_cache (NULL, chain->quote_age,
                                      chain->u.u_a.quotes),
-                        chain->u.u_a.flatten, NULL, NULL, false);
+                        argv->flatten || chain->u.u_a.flatten, NULL, NULL,
+                        false);
              break;
            default:
              assert (!"arg_text");
@@ -953,6 +965,7 @@ arg_text (macro_arguments *argv, unsigned int index)
        }
       obstack_1grow (obs, '\0');
       return (char *) obstack_finish (obs);
+    case TOKEN_FUNC:
     default:
       break;
     }
@@ -967,8 +980,8 @@ arg_text (macro_arguments *argv, unsigned int index)
 bool
 arg_equal (macro_arguments *argv, unsigned int indexa, unsigned int indexb)
 {
-  token_data *ta = arg_token (argv, indexa, NULL);
-  token_data *tb = arg_token (argv, indexb, NULL);
+  token_data *ta = arg_token (argv, indexa, NULL, false);
+  token_data *tb = arg_token (argv, indexb, NULL, false);
   token_chain tmpa;
   token_chain tmpb;
   token_chain *ca = &tmpa;
@@ -985,30 +998,45 @@ arg_equal (macro_arguments *argv, unsigned int indexa, 
unsigned int indexb)
                       TOKEN_DATA_LEN (ta)) == 0);
 
   /* Convert both arguments to chains, if not one already.  */
-  /* TODO - allow builtin tokens in the comparison?  */
-  if (TOKEN_DATA_TYPE (ta) == TOKEN_TEXT)
+  switch (TOKEN_DATA_TYPE (ta))
     {
+    case TOKEN_TEXT:
       tmpa.next = NULL;
       tmpa.type = CHAIN_STR;
       tmpa.u.u_s.str = TOKEN_DATA_TEXT (ta);
       tmpa.u.u_s.len = TOKEN_DATA_LEN (ta);
-    }
-  else
-    {
-      assert (TOKEN_DATA_TYPE (ta) == TOKEN_COMP);
+      break;
+    case TOKEN_FUNC:
+      tmpa.next = NULL;
+      tmpa.type = CHAIN_FUNC;
+      tmpa.u.func = TOKEN_DATA_FUNC (ta);
+      break;
+    case TOKEN_COMP:
       ca = ta->u.u_c.chain;
+      break;
+    default:
+      assert (!"arg_equal");
+      abort ();
     }
-  if (TOKEN_DATA_TYPE (tb) == TOKEN_TEXT)
+  switch (TOKEN_DATA_TYPE (tb))
     {
+    case TOKEN_TEXT:
       tmpb.next = NULL;
       tmpb.type = CHAIN_STR;
       tmpb.u.u_s.str = TOKEN_DATA_TEXT (tb);
       tmpb.u.u_s.len = TOKEN_DATA_LEN (tb);
-    }
-  else
-    {
-      assert (TOKEN_DATA_TYPE (tb) == TOKEN_COMP);
+      break;
+    case TOKEN_FUNC:
+      tmpb.next = NULL;
+      tmpb.type = CHAIN_FUNC;
+      tmpb.u.func = TOKEN_DATA_FUNC (tb);
+      break;
+    case TOKEN_COMP:
       cb = tb->u.u_c.chain;
+      break;
+    default:
+      assert (!"arg_equal");
+      abort ();
     }
 
   /* Compare each link of the chain.  */
@@ -1042,6 +1070,14 @@ arg_equal (macro_arguments *argv, unsigned int indexa, 
unsigned int indexb)
          cb = &tmpb;
          continue;
        }
+      if (ca->type == CHAIN_FUNC)
+       {
+         if (cb->type != CHAIN_FUNC || ca->u.func != cb->u.func)
+           return false;
+         ca = ca->next;
+         cb = cb->next;
+         continue;
+       }
       assert (ca->type == CHAIN_STR && cb->type == CHAIN_STR);
       if (ca->u.u_s.len == cb->u.u_s.len)
        {
@@ -1091,7 +1127,7 @@ arg_empty (macro_arguments *argv, unsigned int index)
     return argv->argv0_len == 0;
   if (index >= argv->argc)
     return true;
-  return arg_token (argv, index, NULL) == &empty_token;
+  return arg_token (argv, index, NULL, false) == &empty_token;
 }
 
 /* Given ARGV, return the length of argument INDEX.  Abort if the
@@ -1107,7 +1143,7 @@ arg_len (macro_arguments *argv, unsigned int index)
     return argv->argv0_len;
   if (index >= argv->argc)
     return 0;
-  token = arg_token (argv, index, NULL);
+  token = arg_token (argv, index, NULL, false);
   switch (TOKEN_DATA_TYPE (token))
     {
     case TOKEN_TEXT:
@@ -1126,6 +1162,10 @@ arg_len (macro_arguments *argv, unsigned int index)
            case CHAIN_STR:
              len += chain->u.u_s.len;
              break;
+           case CHAIN_FUNC:
+             /* TODO concatenate builtins.  */
+             assert (!"implemented");
+             abort ();
            case CHAIN_ARGV:
              i = chain->u.u_a.index;
              limit = chain->u.u_a.argv->argc - i - chain->u.u_a.skip_last;
@@ -1138,8 +1178,8 @@ arg_len (macro_arguments *argv, unsigned int index)
              while (limit--)
                {
                  /* TODO handle builtin concatenation.  */
-                 if (TOKEN_DATA_TYPE (arg_token (chain->u.u_a.argv, i,
-                                                 NULL)) == TOKEN_FUNC)
+                 if (TOKEN_DATA_TYPE (arg_token (chain->u.u_a.argv, i, NULL,
+                                                 false)) == TOKEN_FUNC)
                    assert (argv->flatten);
                  else
                    len += arg_len (chain->u.u_a.argv, i);
@@ -1154,6 +1194,7 @@ arg_len (macro_arguments *argv, unsigned int index)
        }
       assert (len);
       return len;
+    case TOKEN_FUNC:
     default:
       break;
     }
@@ -1168,7 +1209,7 @@ arg_func (macro_arguments *argv, unsigned int index)
 {
   token_data *token;
 
-  token = arg_token (argv, index, NULL);
+  token = arg_token (argv, index, NULL, false);
   assert (TOKEN_DATA_TYPE (token) == TOKEN_FUNC);
   return TOKEN_DATA_FUNC (token);
 }
@@ -1217,7 +1258,7 @@ arg_print (struct obstack *obs, macro_arguments *argv, 
unsigned int index,
       if (use_sep && shipout_string_trunc (obs, sep, sep_len, plen))
        return true;
       use_sep = true;
-      token = arg_token (argv, i, NULL);
+      token = arg_token (argv, i, NULL, flatten);
       switch (TOKEN_DATA_TYPE (token))
        {
        case TOKEN_TEXT:
@@ -1247,6 +1288,10 @@ arg_print (struct obstack *obs, macro_arguments *argv, 
unsigned int index,
                                            chain->u.u_s.len, &len))
                    done = true;
                  break;
+               case CHAIN_FUNC:
+                 func_print (obs, find_builtin_by_addr (chain->u.func),
+                             flatten, quotes);
+                 break;
                case CHAIN_ARGV:
                  if (arg_print (obs, chain->u.u_a.argv, chain->u.u_a.index,
                                 quote_cache (NULL, chain->quote_age,
@@ -1408,8 +1453,8 @@ make_argv_ref (macro_arguments *argv, const char *argv0, 
size_t argv0_len,
   return new_argv;
 }
 
-/* Push argument INDEX from ARGV, which must be a text token, onto the
-   expansion stack OBS for rescanning.  */
+/* Push argument INDEX from ARGV onto the expansion stack OBS for
+   rescanning.  */
 void
 push_arg (struct obstack *obs, macro_arguments *argv, unsigned int index)
 {
@@ -1425,18 +1470,17 @@ push_arg (struct obstack *obs, macro_arguments *argv, 
unsigned int index)
   push_arg_quote (obs, argv, index, NULL);
 }
 
-/* Push argument INDEX from ARGV, which must be a text token, onto the
-   expansion stack OBS for rescanning.  INDEX must be > 0, < argc.
-   QUOTES determines any quote delimiters that were in effect when the
-   reference was created.  */
+/* Push argument INDEX from ARGV onto the expansion stack OBS for
+   rescanning.  INDEX must be > 0, < argc.  QUOTES determines any
+   quote delimiters that were in effect when the reference was
+   created.  */
 void
 push_arg_quote (struct obstack *obs, macro_arguments *argv, unsigned int index,
                const string_pair *quotes)
 {
   int level;
-  token_data *token = arg_token (argv, index, &level);
+  token_data *token = arg_token (argv, index, &level, false);
 
-  /* TODO handle func tokens.  */
   if (quotes)
     obstack_grow (obs, quotes->str1, quotes->len1);
   if (push_token (token, level, argv->inuse))
@@ -1465,8 +1509,7 @@ push_args (struct obstack *obs, macro_arguments *argv, 
bool skip, bool quote)
       return;
     }
 
-  /* TODO allow shift, $@, to push builtins without flatten.  */
-  token = make_argv_ref_token (&td, obs, -1, argv, i, true,
+  token = make_argv_ref_token (&td, obs, -1, argv, i, argv->flatten,
                               quote ? &curr_quote : NULL);
   assert (token);
   if (push_token (token, -1, argv->inuse))
-- 
1.5.4


reply via email to

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