m4-commit
[Top][All Lists]
Advanced

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

[SCM] GNU M4 source repository branch, master, updated. cvs-readonly-82-


From: Eric Blake
Subject: [SCM] GNU M4 source repository branch, master, updated. cvs-readonly-82-ga2df6b4
Date: Fri, 14 Mar 2008 13:08:12 +0000

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

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

The branch, master has been updated
       via  a2df6b461c098df5a505d79d119538b3a294e301 (commit)
       via  2f49d755e50a574e5a772893f3f5d683495084bc (commit)
       via  d621af1bae17e8f6dc2384b71acab9ef21ef51bd (commit)
      from  951a9ef5bbd7eda50bba0ec8b28d57bfbf85c87e (commit)

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

- Log -----------------------------------------------------------------
commit a2df6b461c098df5a505d79d119538b3a294e301
Author: Eric Blake <address@hidden>
Date:   Thu Mar 13 21:01:39 2008 -0600

    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>

commit 2f49d755e50a574e5a772893f3f5d683495084bc
Author: Eric Blake <address@hidden>
Date:   Thu Mar 13 13:48:49 2008 -0600

    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>

commit d621af1bae17e8f6dc2384b71acab9ef21ef51bd
Author: Eric Blake <address@hidden>
Date:   Thu Mar 13 13:43:15 2008 -0600

    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>

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

Summary of changes:
 ChangeLog         |  109 ++++++++++++++++++++++++
 NEWS              |   11 +++
 doc/m4.texinfo    |  105 ++++++++++++++++++++---
 m4/builtin.c      |   88 +++++++++++++------
 m4/input.c        |  242 ++++++++++++++++++++++++++++++++++++----------------
 m4/m4module.h     |   89 ++++++++++++++------
 m4/m4private.h    |   53 ++++++++----
 m4/macro.c        |  102 ++++++++++++++--------
 m4/module.c       |  112 ++++++++++++++++--------
 m4/symtab.c       |   51 +++++------
 m4/utility.c      |   21 +++++
 modules/gnu.c     |   18 ++---
 modules/import.c  |   12 +--
 modules/load.c    |   11 +--
 modules/m4.c      |   89 +++++++++----------
 modules/modtest.c |   10 +--
 modules/mpeval.c  |    9 +--
 modules/perl.c    |    8 +--
 modules/shadow.c  |   10 +--
 modules/stdlib.c  |   10 +--
 modules/time.c    |   13 +--
 tests/macros.at   |    2 +-
 22 files changed, 799 insertions(+), 376 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 1e1d811..8ac919e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,114 @@
 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.
+       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
+       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/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 909f8e3..6d024b3 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
@@ -2274,11 +2277,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')
@@ -2286,18 +2295,54 @@ 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
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
 
-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
@@ -2848,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
 
@@ -2948,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.
@@ -3877,7 +3956,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>) -> ``<changequote>''
 @result{}
 debuglen
 @result{}debuglen
diff --git a/m4/builtin.c b/m4/builtin.c
index 9003b5b..5228abc 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,25 +61,59 @@ 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)));
 
   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 5ebf87f..7d27dad 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,46 +389,29 @@ 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;
-
-  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);
-      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
@@ -461,17 +436,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;
@@ -578,7 +543,7 @@ m4__push_symbol (m4 *context, m4_symbol_value *value, 
size_t level, bool inuse)
          return false;
        }
     }
-  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
@@ -625,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.  */
@@ -738,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)
@@ -792,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))
            {
@@ -836,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);
@@ -865,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))
            {
@@ -903,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,
@@ -1079,20 +1079,33 @@ 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.  If TOKEN is NULL, discard the
+   builtin token instead.  */
 static void
 init_builtin_token (m4 *context, m4_symbol_value *token)
 {
-  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;
+  int ch = next_char (context, false, true);
+  assert (ch == CHAR_BUILTIN);
+
+  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
@@ -1501,7 +1514,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
@@ -1549,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))
@@ -1571,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))
@@ -1579,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)
          {
@@ -1586,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)
@@ -1602,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);
       }
@@ -1630,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 c1f1360..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;
@@ -46,10 +47,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 +91,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
@@ -127,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 *, ...)
@@ -149,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 *);
@@ -252,7 +291,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 +311,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 *);
@@ -295,8 +334,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,8 +342,10 @@ 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 *);
+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 9e10441..5ff7c95 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);
@@ -197,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.  */
 };
 
@@ -215,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  
*/
@@ -248,11 +263,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,20 +363,24 @@ 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,                
\
-                                                   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),                  \
    (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
 
 
@@ -371,7 +390,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..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;
@@ -584,17 +585,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 +616,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;
@@ -665,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;
@@ -1150,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.  */
@@ -1177,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
@@ -1196,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
@@ -1260,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");
@@ -1298,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.  */
@@ -1359,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)
        {
@@ -1510,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,
@@ -1594,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
@@ -1636,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/module.c b/m4/module.c
index 7ef6ab3..2bda5f6 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,46 @@ 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->max_args
+                         || (bp->flags & M4_BUILTIN_FLATTEN_ARGS) == 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 +645,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..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");
@@ -711,12 +702,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
@@ -788,7 +779,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 +787,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 +814,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/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 b4c36af..5309968 100644
--- a/modules/gnu.c
+++ b/modules/gnu.c
@@ -56,12 +56,12 @@
   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  )    \
 
 
 /* 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..359839b 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.
 
@@ -68,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  )    \
@@ -85,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 )    \
 
 
@@ -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
 
@@ -177,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));
     }
 }
 
@@ -205,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 --- */
 
@@ -223,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)
@@ -320,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);
        }
     }
 
@@ -378,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
@@ -620,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);
@@ -861,6 +848,7 @@ M4BUILTIN_HANDLER (m4wrap)
 
 M4BUILTIN_HANDLER (traceon)
 {
+  const char *me = M4ARG (0);
   size_t i;
 
   if (argc == 1)
@@ -868,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)
@@ -881,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"));
 }
 
 
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
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]],


hooks/post-receive
--
GNU M4 source repository




reply via email to

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