[4/18] argv_ref speedup: make argv struct opaque

Eric Blake <[email protected]>
Newsgroups gmane.comp.gnu.m4.patches
Message-ID <[email protected]>
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Another patch ported.  This patch makes the argv struct opaque to all but
the input engine; this part should have no impact on memory and only a
slight impact, if any, on speed.  It also optimizes ifelse to skip strcmp
if lengths are different, so the patch provides a net speedup.  I had to
rework the handling of the obstacks in expand_macro, and a later patch
will rework it yet again.

2007-11-29  Eric Blake  <[email protected]>

        Stage 4: route indir, builtin through ref; make argv opaque.
        * src/m4.h (obstack_regrow): Borrow definition from head.
        (struct token_chain): Add flatten and len members.
        (arg_equal, arg_empty, make_argv_ref): New prototypes.
        (struct macro_arguments): Move...
        * src/macro.c (struct macro_arguments): ...here, making it
        opaque.  Add has_ref member.
        (empty_token): New placeholder, for optimizing comparison with
        empty string.
        (collect_arguments): Change signature, and populate new fields.
        (expand_macro): Alter handling of obstacks.
        (arg_token): New helper method.
        (arg_equal, arg_empty, make_argv_ref): New methods.
        (arg_type, arg_text, arg_len, arg_func): Use new methods.
        * src/builtin.c (m4_ifelse, m4_builtin, m4_indir, m4_eval):
        Likewise.
        * src/format.c (format): Likewise.

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

Eric Blake             [email protected]
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.5 (Cygwin)
Comment: Public key at home.comcast.net/~ericblake/eblake.gpg
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFHT6Fs84KuGfSFAYARAi99AJ46B4ofdmuEFgiquroBzBwrAcdxewCePlYD
/cscYIKrycCpW8ZR3lNsRz8=
=Rhmo
-----END PGP SIGNATURE-----

_______________________________________________
M4-patches mailing list
[email protected]
http://lists.gnu.org/mailman/listinfo/m4-patches
m4.patch300 (text/plain, 60.4 KB)
From 6128996ce3111b5fe1f7d879e11af37c44ee3a92 Mon Sep 17 00:00:00 2001
From: Eric Blake <[email protected]>
Date: Thu, 29 Nov 2007 21:26:22 -0700
Subject: [PATCH] Stage 4: route indir, builtin through ref; make argv opaque.

* m4/system_.h (obstack_regrow): Fix precedence.
* m4/m4module.h (m4_arg_equal, m4_arg_empty, m4_make_argv_ref):
New prototypes.
(struct m4_macro_args): Move...
* m4/m4private.h (struct m4_macro_args): ...here, making it opaque
to modules.  Add has_ref member.
(bool_bitfield): New helper typedef.
(struct m4_symbol_chain): Add flatten and len members.
* m4/macro.c (empty_symbol): New placeholder, for optimizing
comparison with empty string.
(m4_macro_expand_input): Initialize it.
(collect_arguments): Alter signature, and populate new fields.
(trace_pre, trace_post): Remove redundant parameter.
(expand_macro): Alter handling of obstacks.
(m4_arg_symbol): Account for wrapped argv.
(m4_arg_equal, m4_arg_empty, m4_make_argv_ref): New methods.
(m4_arg_text, m4_arg_len, m4_arg_func): Use new methods.
* modules/m4.c (ifelse, syscmd): Likewise.
* modules/evalparse.c (m4_evaluate): Likewise.
(undefine, popdef, m4_dump_symbols): Optimize.
* modules/gnu.c (builtin, indir, esyscmd, debugfile): Use new
methods.
(changesyntax, regexp): Optimize.
* m4/output.c (diversion_storage): Use typedef.

Signed-off-by: Eric Blake <[email protected]>
---
 ChangeLog           |   26 ++++++
 m4/m4module.h       |   25 +----
 m4/m4private.h      |   56 +++++++++---
 m4/macro.c          |  246 ++++++++++++++++++++++++++++++++++++++++-----------
 m4/output.c         |    2 +-
 m4/system_.h        |    6 +-
 modules/evalparse.c |    3 +-
 modules/gnu.c       |   62 ++++---------
 modules/m4.c        |   38 ++++----
 9 files changed, 312 insertions(+), 152 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 695720d..55ee2ea 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,31 @@
 2007-11-29  Eric Blake  <[email protected]>
 
+	Stage 4: route indir, builtin through ref; make argv opaque.
+	* m4/system_.h (obstack_regrow): Fix precedence.
+	* m4/m4module.h (m4_arg_equal, m4_arg_empty, m4_make_argv_ref):
+	New prototypes.
+	(struct m4_macro_args): Move...
+	* m4/m4private.h (struct m4_macro_args): ...here, making it opaque
+	to modules.  Add has_ref member.
+	(bool_bitfield): New helper typedef.
+	(struct m4_symbol_chain): Add flatten and len members.
+	* m4/macro.c (empty_symbol): New placeholder, for optimizing
+	comparison with empty string.
+	(m4_macro_expand_input): Initialize it.
+	(collect_arguments): Alter signature, and populate new fields.
+	(trace_pre, trace_post): Remove redundant parameter.
+	(expand_macro): Alter handling of obstacks.
+	(m4_arg_symbol): Account for wrapped argv.
+	(m4_arg_equal, m4_arg_empty, m4_make_argv_ref): New methods.
+	(m4_arg_text, m4_arg_len, m4_arg_func): Use new methods.
+	* modules/m4.c (ifelse, syscmd): Likewise.
+	* modules/evalparse.c (m4_evaluate): Likewise.
+	(undefine, popdef, m4_dump_symbols): Optimize.
+	* modules/gnu.c (builtin, indir, esyscmd, debugfile): Use new
+	methods.
+	(changesyntax, regexp): Optimize.
+	* m4/output.c (diversion_storage): Use typedef.
+
 	Stage 3b: cache length, rather than computing it, in modules.
 	* m4/hash.c (m4_hash_remove): Avoid double free on remove
 	failure.
diff --git a/m4/m4module.h b/m4/m4module.h
index 7ffaffd..8f3f590 100644
--- a/m4/m4module.h
+++ b/m4/m4module.h
@@ -77,26 +77,6 @@ struct m4_macro
   const char *value;
 };
 
-/* FIXME - make this struct opaque.  */
-struct m4_macro_args
-{
-  /* One more than the highest actual argument.  May be larger than
-     arraylen since the array can refer to multiple arguments via a
-     single $@ reference.  */
-  unsigned int argc;
-  /* False unless the macro expansion refers to $@; determines whether
-     this object can be freed at end of macro expansion or must wait
-     until all references have been rescanned.  */
-  bool inuse;
-  const char *argv0; /* The macro name being expanded.  */
-  size_t argv0_len; /* Length of argv0.  */
-  size_t arraylen; /* True length of allocated elements in array.  */
-  /* Used as a variable-length array, storing information about each
-     argument.  */
-  m4_symbol_value *array[FLEXIBLE_ARRAY_MEMBER];
-};
-
-
 #define M4BUILTIN(name)							\
   static void CONC (builtin_, name)					\
    (m4 *context, m4_obstack *obs, unsigned int argc, m4_macro_args *argv);
@@ -320,8 +300,13 @@ extern m4_symbol_value *m4_arg_symbol	(m4_macro_args *, unsigned int);
 extern bool	m4_is_arg_text		(m4_macro_args *, unsigned int);
 extern bool	m4_is_arg_func		(m4_macro_args *, unsigned int);
 extern const char *m4_arg_text		(m4_macro_args *, unsigned int);
+extern bool	m4_arg_equal		(m4_macro_args *, unsigned int,
+                                         unsigned int);
+extern bool	m4_arg_empty		(m4_macro_args *, unsigned int);
 extern size_t	m4_arg_len		(m4_macro_args *, unsigned int);
 extern m4_builtin_func *m4_arg_func	(m4_macro_args *, unsigned int);
+extern m4_macro_args *m4_make_argv_ref	(m4_macro_args *, const char *, size_t,
+                                         bool, bool);
 
 
 /* --- RUNTIME DEBUGGING --- */
diff --git a/m4/m4private.h b/m4/m4private.h
index 84e7157..8e23e00 100644
--- a/m4/m4private.h
+++ b/m4/m4private.h
@@ -41,6 +41,15 @@ typedef enum {
 #define BIT_SET(flags, bit)	((flags) |= (bit))
 #define BIT_RESET(flags, bit)	((flags) &= ~(bit))
 
+/* Gnulib's stdbool doesn't work with bool bitfields.  For nicer
+   debugging, use bool when we know it works, but use the more
+   portable unsigned int elsewhere.  */
+#if __GNUC__ > 2
+typedef bool bool_bitfield;
+#else
+typedef unsigned int bool_bitfield;
+#endif /* !__GNUC__ */
+
 
 /* --- CONTEXT MANAGEMENT --- */
 
@@ -176,17 +185,19 @@ typedef struct m4_symbol_chain m4_symbol_chain;
 
 struct m4_symbol
 {
-  bool		traced;
-  m4_symbol_value *	value;
+  bool traced;			/* True if this symbol is traced.  */
+  m4_symbol_value *value;	/* Linked list of pushdef'd values.  */
 };
 
 /* Composite symbols are built of a linked list of chain objects.  */
 struct m4_symbol_chain
 {
   m4_symbol_chain *next;/* Pointer to next link of chain.  */
-  char *str;		/* NUL-terminated string if text, else NULL.  */
+  char *str;		/* NUL-terminated string if text, or NULL.  */
+  size_t len;		/* Length of str, or 0.  */
   m4_macro_args *argv;	/* Reference to earlier $@.  */
-  unsigned int index;	/* Index within argv to start reading from.  */
+  unsigned int index;	/* Argument index within argv.  */
+  bool flatten;		/* True to treat builtins as text.  */
 };
 
 /* A symbol value is used both for values associated with a macro
@@ -215,6 +226,29 @@ struct m4_symbol_value
   } u;
 };
 
+/* Structure describing all arguments to a macro, including the macro
+   name at index 0.  */
+struct m4_macro_args
+{
+  /* One more than the highest actual argument.  May be larger than
+     arraylen since the array can refer to multiple arguments via a
+     single $@ reference.  */
+  unsigned int argc;
+  /* False unless the macro expansion refers to $@; determines whether
+     this object can be freed at end of macro expansion or must wait
+     until all references have been rescanned.  */
+  bool_bitfield inuse : 1;
+  /* False if all arguments are just text or func, true if this argv
+     refers to another one.  */
+  bool_bitfield has_ref : 1;
+  const char *argv0; /* The macro name being expanded.  */
+  size_t argv0_len; /* Length of argv0.  */
+  size_t arraylen; /* True length of allocated elements in array.  */
+  /* Used as a variable-length array, storing information about each
+     argument.  */
+  m4_symbol_value *array[FLEXIBLE_ARRAY_MEMBER];
+};
+
 #define VALUE_NEXT(T)		((T)->next)
 #define VALUE_MODULE(T)		((T)->module)
 #define VALUE_FLAGS(T)		((T)->flags)
@@ -223,13 +257,13 @@ struct m4_symbol_value
 #define VALUE_MAX_ARGS(T)	((T)->max_args)
 #define VALUE_PENDING(T)	((T)->pending_expansions)
 
-#define SYMBOL_NEXT(S)		(VALUE_NEXT	     ((S)->value))
-#define SYMBOL_MODULE(S)	(VALUE_MODULE	     ((S)->value))
-#define SYMBOL_FLAGS(S)		(VALUE_FLAGS	     ((S)->value))
-#define SYMBOL_ARG_SIGNATURE(S)	(VALUE_ARG_SIGNATURE ((S)->value))
-#define SYMBOL_MIN_ARGS(S)	(VALUE_MIN_ARGS      ((S)->value))
-#define SYMBOL_MAX_ARGS(S)	(VALUE_MAX_ARGS      ((S)->value))
-#define SYMBOL_PENDING(S)	(VALUE_PENDING       ((S)->value))
+#define SYMBOL_NEXT(S)		(VALUE_NEXT		((S)->value))
+#define SYMBOL_MODULE(S)	(VALUE_MODULE		((S)->value))
+#define SYMBOL_FLAGS(S)		(VALUE_FLAGS		((S)->value))
+#define SYMBOL_ARG_SIGNATURE(S)	(VALUE_ARG_SIGNATURE	((S)->value))
+#define SYMBOL_MIN_ARGS(S)	(VALUE_MIN_ARGS		((S)->value))
+#define SYMBOL_MAX_ARGS(S)	(VALUE_MAX_ARGS		((S)->value))
+#define SYMBOL_PENDING(S)	(VALUE_PENDING		((S)->value))
 
 /* Fast macro versions of symbol table accessor functions,
    that also have an identically named function exported in m4module.h.  */
diff --git a/m4/macro.c b/m4/macro.c
index 5769f99..25fc7e7 100644
--- a/m4/macro.c
+++ b/m4/macro.c
@@ -30,8 +30,7 @@
 #include "intprops.h"
 
 static m4_macro_args *collect_arguments (m4 *, const char *, size_t,
-					 m4_symbol *, m4_obstack *,
-					 unsigned int, m4_obstack *);
+					 m4_symbol *, m4_obstack *);
 static void    expand_macro      (m4 *, const char *, size_t, m4_symbol *);
 static void    expand_token      (m4 *, m4_obstack *, m4__token_type,
 				  m4_symbol_value *, int);
@@ -42,9 +41,9 @@ static void    process_macro	 (m4 *, m4_symbol_value *, m4_obstack *, int,
 
 static void    trace_prepre	 (m4 *, const char *, size_t,
 				  m4_symbol_value *);
-static void    trace_pre	 (m4 *, const char *, size_t, m4_macro_args *);
-static void    trace_post	 (m4 *, const char *, size_t,
-				  m4_macro_args *, m4_input_block *, bool);
+static void    trace_pre	 (m4 *, size_t, m4_macro_args *);
+static void    trace_post	 (m4 *, size_t, m4_macro_args *,
+				  m4_input_block *, bool);
 
 static void    trace_format	 (m4 *, const char *, ...)
   M4_GNUC_PRINTF (2, 3);
@@ -63,13 +62,17 @@ static size_t macro_call_id = 0;
    argv_stack.  This stack can be used simultaneously by multiple
    macro calls, using obstack_regrow to handle partial objects
    embedded in the stack.  */
-static struct obstack argc_stack;
+static m4_obstack argc_stack;
 
 /* The shared stack of pointers to collected arguments for macro
    calls.  This object is never finished; we exploit the fact that
    obstack_blank is documented to take a negative size to reduce the
    size again.  */
-static struct obstack argv_stack;
+static m4_obstack argv_stack;
+
+/* A placeholder symbol value representing the empty string, used to
+   optimize checks for emptiness.  */
+static m4_symbol_value empty_symbol;
 
 /* This function reads all input, and expands each token, one at a time.  */
 void
@@ -82,6 +85,8 @@ m4_macro_expand_input (m4 *context)
   obstack_init (&argc_stack);
   obstack_init (&argv_stack);
 
+  m4_set_symbol_value_text (&empty_symbol, "", 0);
+
   while ((type = m4__next_token (context, &token, &line, NULL))
 	 != M4_TOKEN_EOF)
     expand_token (context, (m4_obstack *) NULL, type, &token, line);
@@ -251,7 +256,8 @@ expand_argument (m4 *context, m4_obstack *obs, m4_symbol_value *argp,
 static void
 expand_macro (m4 *context, const char *name, size_t len, m4_symbol *symbol)
 {
-  char *argc_base = NULL;	/* Base of argc_stack on entry.  */
+  void *argc_base = NULL;	/* Base of argc_stack on entry.  */
+  void *argv_base = NULL;	/* Base of argv_stack on entry.  */
   unsigned int argc_size;	/* Size of argc_stack on entry.  */
   unsigned int argv_size;	/* Size of argv_stack on entry.  */
   m4_macro_args *argv;
@@ -296,17 +302,14 @@ recursion limit of %zu exceeded, use -L<N> to change it"),
 
   argc_size = obstack_object_size (&argc_stack);
   argv_size = obstack_object_size (&argv_stack);
-  if (0 < argc_size)
-    argc_base = obstack_finish (&argc_stack);
+  argc_base = obstack_finish (&argc_stack);
+  if (0 < argv_size)
+    argv_base = obstack_finish (&argv_stack);
 
   if (traced && m4_is_debug_bit (context, M4_DEBUG_TRACE_CALL))
     trace_prepre (context, name, my_call_id, value);
 
-  argv = collect_arguments (context, name, len, symbol, &argv_stack,
-			    argv_size, &argc_stack);
-  /* Calling collect_arguments invalidated name, but we copied it as
-     argv[0].  */
-  name = argv->argv0;
+  argv = collect_arguments (context, name, len, symbol, &argc_stack);
 
   loc_close_file = m4_get_current_file (context);
   loc_close_line = m4_get_current_line (context);
@@ -314,14 +317,14 @@ recursion limit of %zu exceeded, use -L<N> to change it"),
   m4_set_current_line (context, loc_open_line);
 
   if (traced)
-    trace_pre (context, name, my_call_id, argv);
+    trace_pre (context, my_call_id, argv);
 
   expansion = m4_push_string_init (context);
   m4_macro_call (context, value, expansion, argv->argc, argv);
   expanded = m4_push_string_finish ();
 
   if (traced)
-    trace_post (context, name, my_call_id, argv, expanded, trace_expansion);
+    trace_post (context, my_call_id, argv, expanded, trace_expansion);
 
   m4_set_current_file (context, loc_close_file);
   m4_set_current_line (context, loc_close_line);
@@ -335,20 +338,21 @@ recursion limit of %zu exceeded, use -L<N> to change it"),
   if (0 < argc_size)
     obstack_regrow (&argc_stack, argc_base, argc_size);
   else
-    obstack_free (&argc_stack, (void *) name);
-  obstack_blank (&argv_stack, argv_size - obstack_object_size (&argv_stack));
+    obstack_free (&argc_stack, argc_base);
+  if (0 < argv_size)
+    obstack_regrow (&argv_stack, argv_base, argv_size);
+  else
+    obstack_free (&argv_stack, argv);
 }
 
 /* Collect all the arguments to a call of the macro SYMBOL (called
    NAME, with length LEN).  The arguments are stored on the obstack
    ARGUMENTS and a table of pointers to the arguments on the obstack
-   ARGPTR.  ARGPTR is an incomplete object, currently occupying
-   ARGV_BASE bytes.  Return the object describing all of the macro
+   argv_stack.  Return the object describing all of the macro
    arguments.  */
 static m4_macro_args *
 collect_arguments (m4 *context, const char *name, size_t len,
-		   m4_symbol *symbol, m4_obstack *argptr,
-		   unsigned int argv_base, m4_obstack *arguments)
+		   m4_symbol *symbol, m4_obstack *arguments)
 {
   m4_symbol_value token;
   m4_symbol_value *tokenp;
@@ -361,10 +365,13 @@ collect_arguments (m4 *context, const char *name, size_t len,
 
   args.argc = 1;
   args.inuse = false;
+  args.has_ref = false;
+  /* FIXME - add accessor to symtab that returns name from the hash
+     table, so we don't have to copy it here.  */
   args.argv0 = (char *) obstack_copy0 (arguments, name, len);
   args.argv0_len = len;
   args.arraylen = 0;
-  obstack_grow (argptr, &args, offsetof (m4_macro_args, array));
+  obstack_grow (&argv_stack, &args, offsetof (m4_macro_args, array));
   name = args.argv0;
 
   if (m4__next_token_is_open (context))
@@ -374,20 +381,20 @@ collect_arguments (m4 *context, const char *name, size_t len,
 	{
 	  more_args = expand_argument (context, arguments, &token, name);
 
-	  if (!groks_macro_args && m4_is_symbol_value_func (&token))
-	    {
-	      VALUE_MODULE (&token) = NULL;
-	      m4_set_symbol_value_text (&token, "", 0);
-	    }
-	  tokenp = (m4_symbol_value *) obstack_copy (arguments, &token,
-						     sizeof token);
-	  obstack_ptr_grow (argptr, tokenp);
+	  if ((m4_is_symbol_value_text (&token)
+	       && !m4_get_symbol_value_len (&token))
+	      || (!groks_macro_args && m4_is_symbol_value_func (&token)))
+	    tokenp = &empty_symbol;
+	  else
+	    tokenp = (m4_symbol_value *) obstack_copy (arguments, &token,
+						       sizeof *tokenp);
+	  obstack_ptr_grow (&argv_stack, tokenp);
 	  args.arraylen++;
 	  args.argc++;
 	}
       while (more_args);
     }
-  argv = (m4_macro_args *) ((char *) obstack_base (argptr) + argv_base);
+  argv = (m4_macro_args *) obstack_finish (&argv_stack);
   argv->argc = args.argc;
   argv->arraylen = args.arraylen;
   return argv;
@@ -536,11 +543,11 @@ process_macro (m4 *context, m4_symbol_value *value, m4_obstack *obs,
 
 
 
-/* The rest of this file contains the functions for macro tracing output.
-   All tracing output for a macro call is collected on an obstack TRACE,
-   and printed whenever the line is complete.  This prevents tracing
-   output from interfering with other debug messages generated by the
-   various builtins.  */
+/* The next portion of this file contains the functions for macro
+   tracing output.  All tracing output for a macro call is collected
+   on an obstack TRACE, and printed whenever the line is complete.
+   This prevents tracing output from interfering with other debug
+   messages generated by the various builtins.  */
 
 /* Tracing output is formatted here, by a simplified printf-to-obstack
    function trace_format ().  Understands only %s, %d, %zu (size_t
@@ -653,13 +660,13 @@ trace_prepre (m4 *context, const char *name, size_t id, m4_symbol_value *value)
 /* Format the parts of a trace line, that can be made before the macro is
    actually expanded.  Used from expand_macro ().  */
 static void
-trace_pre (m4 *context, const char *name, size_t id, m4_macro_args *argv)
+trace_pre (m4 *context, size_t id, m4_macro_args *argv)
 {
   unsigned int i;
   unsigned int argc = m4_arg_argc (argv);
 
   trace_header (context, id);
-  trace_format (context, "%s", name);
+  trace_format (context, "%s", M4ARG (0));
 
   if (1 < argc && m4_is_debug_bit (context, M4_DEBUG_TRACE_ARGS))
     {
@@ -686,9 +693,8 @@ trace_pre (m4 *context, const char *name, size_t id, m4_macro_args *argv)
 /* Format the final part of a trace line and print it all.  Used from
    expand_macro ().  */
 static void
-trace_post (m4 *context, const char *name, size_t id,
-	    m4_macro_args *argv, m4_input_block *expanded,
-	    bool trace_expansion)
+trace_post (m4 *context, size_t id, m4_macro_args *argv,
+	    m4_input_block *expanded, bool trace_expansion)
 {
   if (trace_expansion)
     {
@@ -703,12 +709,43 @@ trace_post (m4 *context, const char *name, size_t id,
 /* Accessors into m4_macro_args.  */
 
 /* Given ARGV, return the symbol value at the specified INDEX, which
-   must be non-zero and less than argc.  */
+   must be non-zero.  */
 m4_symbol_value *
 m4_arg_symbol (m4_macro_args *argv, unsigned int index)
 {
-  assert (index && index < argv->argc);
-  return argv->array[index - 1];
+  unsigned int i;
+  m4_symbol_value *value;
+
+  assert (index);
+  if (argv->argc <= index)
+    return &empty_symbol;
+
+  if (!argv->has_ref)
+    return argv->array[index - 1];
+  /* Must cycle through all array slots until we find index, since
+     wrappers can contain multiple arguments.  */
+  for (i = 0; i < argv->arraylen; i++)
+    {
+      value = argv->array[i];
+      if (value->type == M4_SYMBOL_COMP)
+	{
+	  m4_symbol_chain *chain = value->u.chain;
+	  /* TODO - for now we support only a single $@ chain.  */
+	  assert (!chain->next && !chain->str);
+	  if (index < chain->argv->argc - (chain->index - 1))
+	    {
+	      value = m4_arg_symbol (chain->argv, chain->index - 1 + index);
+	      if (chain->flatten && m4_is_symbol_value_func (value))
+		value = &empty_symbol;
+	      break;
+	    }
+	  index -= chain->argv->argc - chain->index;
+	}
+      else if (--index == 0)
+	break;
+    }
+  assert (value->type != M4_SYMBOL_COMP);
+  return value;
 }
 
 /* Given ARGV, return true if argument INDEX is text.  Index 0 is
@@ -737,13 +774,45 @@ m4_is_arg_func (m4_macro_args *argv, unsigned int index)
 const char *
 m4_arg_text (m4_macro_args *argv, unsigned int index)
 {
+  m4_symbol_value *value;
+
   if (index == 0)
     return argv->argv0;
   if (argv->argc <= index)
     return "";
-  if (!m4_is_symbol_value_text (argv->array[index - 1]))
+  value = m4_arg_symbol (argv, index);
+  if (!m4_is_symbol_value_text (value))
     return NULL;
-  return m4_get_symbol_value_text (argv->array[index - 1]);
+  return m4_get_symbol_value_text (value);
+}
+
+/* Given ARGV, compare text arguments INDEXA and INDEXB for equality.
+   Both indices must be non-zero.  Return true if the arguments
+   contain the same contents; often more efficient than
+   !strcmp (m4_arg_text (argv, indexa), m4_arg_text (argv, indexb)).  */
+bool
+m4_arg_equal (m4_macro_args *argv, unsigned int indexa, unsigned int indexb)
+{
+  m4_symbol_value *sa = m4_arg_symbol (argv, indexa);
+  m4_symbol_value *sb = m4_arg_symbol (argv, indexb);
+
+  if (sa == &empty_symbol || sb == &empty_symbol)
+    return sa == sb;
+  /* TODO - allow builtin tokens in the comparison?  */
+  assert (m4_is_symbol_value_text (sa) && m4_is_symbol_value_text (sb));
+  return (m4_get_symbol_value_len (sa) == m4_get_symbol_value_len (sb)
+	  && strcmp (m4_get_symbol_value_text (sa),
+		     m4_get_symbol_value_text (sb)) == 0);
+}
+
+/* Given ARGV, return true if argument INDEX is the empty string.
+   This gives the same result as comparing m4_arg_len against 0, but
+   is often faster.  */
+bool
+m4_arg_empty (m4_macro_args *argv, unsigned int index)
+{
+  return (index ? m4_arg_symbol (argv, index) == &empty_symbol
+	  : !argv->argv0_len);
 }
 
 /* Given ARGV, return the length of argument INDEX, or SIZE_MAX if the
@@ -751,13 +820,16 @@ m4_arg_text (m4_macro_args *argv, unsigned int index)
 size_t
 m4_arg_len (m4_macro_args *argv, unsigned int index)
 {
+  m4_symbol_value *value;
+
   if (index == 0)
     return argv->argv0_len;
   if (argv->argc <= index)
     return 0;
-  if (!m4_is_symbol_value_text (argv->array[index - 1]))
+  value = m4_arg_symbol (argv, index);
+  if (!m4_is_symbol_value_text (value))
     return SIZE_MAX;
-  return m4_get_symbol_value_len (argv->array[index - 1]);
+  return m4_get_symbol_value_len (value);
 }
 
 /* Given ARGV, return the builtin function referenced by argument
@@ -766,10 +838,78 @@ m4_arg_len (m4_macro_args *argv, unsigned int index)
 m4_builtin_func *
 m4_arg_func (m4_macro_args *argv, unsigned int index)
 {
-  if (index == 0 || argv->argc <= index
-      || !m4_is_symbol_value_func (argv->array[index - 1]))
+  m4_symbol_value *value;
+
+  if (index == 0 || argv->argc <= index)
+    return NULL;
+  value = m4_arg_symbol (argv, index);
+  if (!m4_is_symbol_value_func (value))
     return NULL;
-  return m4_get_symbol_value_func (argv->array[index - 1]);
+  return m4_get_symbol_value_func (value);
+}
+
+/* Create a new argument object using the same obstack as ARGV; thus,
+   the new object will automatically be freed when the original is
+   freed.  Explicitly set the macro name (argv[0]) from ARGV0 with
+   length ARGV0_LEN.  If SKIP, set argv[1] of the new object to
+   argv[2] of the old, otherwise the objects share all arguments.  If
+   FLATTEN, any builtins in ARGV are flattened to an empty string when
+   referenced through the new object.  */
+m4_macro_args *
+m4_make_argv_ref (m4_macro_args *argv, const char *argv0, size_t argv0_len,
+		  bool skip, bool flatten)
+{
+  m4_macro_args *new_argv;
+  m4_symbol_value *value;
+  m4_symbol_chain *chain;
+  unsigned int index = skip ? 2 : 1;
+
+  assert (obstack_object_size (&argv_stack) == 0);
+  /* When making a reference through a reference, point to the
+     original if possible.  */
+  if (argv->has_ref)
+    {
+      /* TODO for now we support only a single-length $@ chain.  */
+      assert (argv->arraylen == 1 && argv->array[0]->type == M4_SYMBOL_COMP);
+      chain = argv->array[0]->u.chain;
+      assert (!chain->next && !chain->str);
+      argv = chain->argv;
+      index += chain->index - 1;
+    }
+  if (argv->argc <= index)
+    {
+      new_argv = (m4_macro_args *) obstack_alloc (&argv_stack,
+						  offsetof (m4_macro_args,
+							    array));
+      new_argv->arraylen = 0;
+      new_argv->has_ref = false;
+    }
+  else
+    {
+      new_argv = (m4_macro_args *) obstack_alloc (&argv_stack,
+						  (offsetof (m4_macro_args,
+							     array)
+						   + sizeof value));
+      value = (m4_symbol_value *) obstack_alloc (&argv_stack, sizeof *value);
+      chain = (m4_symbol_chain *) obstack_alloc (&argv_stack, sizeof *chain);
+      new_argv->arraylen = 1;
+      new_argv->array[0] = value;
+      new_argv->has_ref = true;
+      value->type = M4_SYMBOL_COMP;
+      value->u.chain = chain;
+      chain->next = NULL;
+      chain->str = NULL;
+      chain->len = 0;
+      chain->argv = argv;
+      chain->index = index;
+      chain->flatten = flatten;
+    }
+  /* TODO - should argv->inuse be set?  */
+  new_argv->argc = argv->argc - (index - 1);
+  new_argv->inuse = false;
+  new_argv->argv0 = argv0;
+  new_argv->argv0_len = argv0_len;
+  return new_argv;
 }
 
 /* Define these last, so that earlier uses can benefit from the macros
diff --git a/m4/output.c b/m4/output.c
index ed2a451..8089073 100644
--- a/m4/output.c
+++ b/m4/output.c
@@ -83,7 +83,7 @@ static m4_diversion div0;
 static m4_diversion *free_list;
 
 /* Obstack from which diversion storage is allocated.  */
-static struct obstack diversion_storage;
+static m4_obstack diversion_storage;
 
 /* Total size of all in-memory buffer sizes.  */
 static size_t total_buffer_size;
diff --git a/m4/system_.h b/m4/system_.h
index e014d75..64ca73c 100644
--- a/m4/system_.h
+++ b/m4/system_.h
@@ -53,9 +53,9 @@
    of an object on the stack.  Reopen OBJECT (previously returned by
    obstack_alloc or obstack_finish) with SIZE for additional growth,
    freeing all objects that occur later in the stack.  */
-#define obstack_regrow(OBS, OBJECT, SIZE)               \
-  (obstack_free (OBS, (char *)(OBJECT) + SIZE),         \
-   (OBS)->object_base = (char *)(OBJECT))
+#define obstack_regrow(OBS, OBJECT, SIZE)		\
+  (obstack_free (OBS, (char *) (OBJECT) + (SIZE)),	\
+   (OBS)->object_base = (char *) (OBJECT))
 
 /* In addition to EXIT_SUCCESS and EXIT_FAILURE, m4 can fail with version
    mismatch when trying to load a frozen file produced by a newer m4 than
diff --git a/modules/evalparse.c b/modules/evalparse.c
index e21a081..39b0d41 100644
--- a/modules/evalparse.c
+++ b/modules/evalparse.c
@@ -896,7 +896,8 @@ m4_evaluate (m4 *context, m4_obstack *obs, unsigned int argc,
   eval_token	et;
   eval_error	err	= NO_ERROR;
 
-  if (*M4ARG (2) && !m4_numeric_arg (context, me, M4ARG (2), &radix))
+  if (!m4_arg_empty (argv, 2)
+      && !m4_numeric_arg (context, me, M4ARG (2), &radix))
     return;
 
   if (radix < 1 || radix > 36)
diff --git a/modules/gnu.c b/modules/gnu.c
index bc34692..3c772c5 100644
--- a/modules/gnu.c
+++ b/modules/gnu.c
@@ -444,26 +444,11 @@ M4BUILTIN_HANDLER (builtin)
 			    bp->min_args, bp->max_args,
 			    (bp->flags & M4_BUILTIN_SIDE_EFFECT) != 0))
 	    {
-	      unsigned int i;
-	      /* TODO - make use of $@ reference.  */
-	      /* TODO - add accessor that performs this construction.  */
 	      m4_macro_args *new_argv;
-	      new_argv = xmalloc (offsetof (m4_macro_args, array)
-				  + ((argc - 2) * sizeof (m4_symbol_value *)));
-	      new_argv->argc = argc - 1;
-	      new_argv->inuse = false;
-	      new_argv->argv0 = name;
-	      new_argv->argv0_len = m4_arg_len (argv, 1);
-	      new_argv->arraylen = argc - 2;
-	      memcpy (&new_argv->array[0], &argv->array[1],
-		      (argc - 2) * sizeof (m4_symbol_value *));
-	      if ((bp->flags & M4_BUILTIN_GROKS_MACRO) == 0)
-		for (i = 2; i < argc; i++)
-		  if (!m4_is_arg_text (argv, i))
-		    m4_set_symbol_value_text (m4_arg_symbol (new_argv, i - 1),
-					      "", 0);
+	      bool flatten = (bp->flags & M4_BUILTIN_GROKS_MACRO) == 0;
+	      new_argv = m4_make_argv_ref (argv, name, m4_arg_len (argv, 1),
+					   true, flatten);
 	      bp->func (context, obs, argc - 1, new_argv);
-	      free (new_argv);
 	    }
 	  free (value);
 	}
@@ -508,6 +493,7 @@ M4BUILTIN_HANDLER (changeresyntax)
  **/
 M4BUILTIN_HANDLER (changesyntax)
 {
+  const char *me = M4ARG (0);
   M4_MODULE_IMPORT (m4, m4_expand_ranges);
 
   if (m4_expand_ranges)
@@ -533,7 +519,7 @@ M4BUILTIN_HANDLER (changesyntax)
 	    }
 	  if (m4_set_syntax (M4SYNTAX, key, action,
 			     key ? m4_expand_ranges (spec, obs) : "") < 0)
-	    m4_warn (context, 0, M4ARG (0), _("undefined syntax code: `%c'"),
+	    m4_warn (context, 0, me, _("undefined syntax code: `%c'"),
 		     key);
 	}
     }
@@ -554,7 +540,7 @@ M4BUILTIN_HANDLER (debugfile)
 
   if (argc == 1)
     m4_debug_set_output (context, me, NULL);
-  else if (m4_get_safer_opt (context) && *M4ARG (1))
+  else if (m4_get_safer_opt (context) && !m4_arg_empty (argv, 1))
     m4_error (context, 0, 0, me, _("disabled by --safer"));
   else if (!m4_debug_set_output (context, me, M4ARG (1)))
     m4_error (context, 0, errno, me, _("cannot set debug file `%s'"),
@@ -613,6 +599,7 @@ M4BUILTIN_HANDLER (debugmode)
 
 M4BUILTIN_HANDLER (esyscmd)
 {
+  const char *me = M4ARG (0);
   M4_MODULE_IMPORT (m4, m4_set_sysval);
   M4_MODULE_IMPORT (m4, m4_sysval_flush);
 
@@ -623,12 +610,12 @@ M4BUILTIN_HANDLER (esyscmd)
 
       if (m4_get_safer_opt (context))
 	{
-	  m4_error (context, 0, 0, M4ARG (0), _("disabled by --safer"));
+	  m4_error (context, 0, 0, me, _("disabled by --safer"));
 	  return;
 	}
 
       /* Optimize the empty command.  */
-      if (*M4ARG (1) == '\0')
+      if (m4_arg_empty (argv, 1))
 	{
 	  m4_set_sysval (0);
 	  return;
@@ -639,14 +626,14 @@ M4BUILTIN_HANDLER (esyscmd)
       pin = popen (M4ARG (1), "r");
       if (pin == NULL)
 	{
-	  m4_error (context, 0, errno, M4ARG (0),
+	  m4_error (context, 0, errno, me,
 		    _("cannot open pipe to command `%s'"), M4ARG (1));
 	  m4_set_sysval (-1);
 	}
       else
 	{
 	  while ((ch = getc (pin)) != EOF)
-	    obstack_1grow (obs, (char) ch);
+	    obstack_1grow (obs, ch);
 	  m4_set_sysval (pclose (pin));
 	}
     }
@@ -690,27 +677,12 @@ M4BUILTIN_HANDLER (indir)
 	m4_warn (context, 0, me, _("undefined macro `%s'"), name);
       else
 	{
-	  unsigned int i;
-	  /* TODO - make use of $@ reference.  */
-	  /* TODO - add accessor that performs this construction.  */
 	  m4_macro_args *new_argv;
-	  new_argv = xmalloc (offsetof (m4_macro_args, array)
-			      + ((argc - 2) * sizeof (m4_symbol_value *)));
-	  new_argv->argc = argc - 1;
-	  new_argv->inuse = false;
-	  new_argv->argv0 = name;
-	  new_argv->argv0_len = m4_arg_len (argv, 1);
-	  new_argv->arraylen = argc - 2;
-	  memcpy (&new_argv->array[0], &argv->array[1],
-		  (argc - 2) * sizeof (m4_symbol_value *));
-	  if (!m4_symbol_groks_macro (symbol))
-	    for (i = 2; i < argc; i++)
-	      if (!m4_is_arg_text (argv, i))
-		m4_set_symbol_value_text (m4_arg_symbol (new_argv, i - 1),
-					  "", 0);
+	  bool flatten = !m4_symbol_groks_macro (symbol);
+	  new_argv = m4_make_argv_ref (argv, name, m4_arg_len (argv, 1), true,
+				       flatten);
 	  m4_macro_call (context, m4_get_symbol_value (symbol), obs,
 			 argc - 1, new_argv);
-	  free (new_argv);
 	}
     }
 }
@@ -793,6 +765,7 @@ M4BUILTIN_HANDLER (patsubst)
 M4BUILTIN_HANDLER (regexp)
 {
   const char *me;		/* name of this macro */
+  const char *victim;		/* string to search */
   const char *pattern;		/* regular expression */
   const char *replace;		/* optional replacement string */
   m4_pattern_buffer *buf;	/* compiled regular expression */
@@ -845,8 +818,9 @@ M4BUILTIN_HANDLER (regexp)
   if (!buf)
     return;
 
+  victim = M4ARG (1);
   len = m4_arg_len (argv, 1);
-  startpos = regexp_search (buf, M4ARG (1), len, 0, len, replace == NULL);
+  startpos = regexp_search (buf, victim, len, 0, len, replace == NULL);
 
   if (startpos == -2)
     {
@@ -858,7 +832,7 @@ M4BUILTIN_HANDLER (regexp)
   if (replace == NULL)
     m4_shipout_int (obs, startpos);
   else if (startpos >= 0)
-    substitute (context, obs, me, M4ARG (1), replace, buf);
+    substitute (context, obs, me, victim, replace, buf);
 }
 
 
diff --git a/modules/m4.c b/modules/m4.c
index 827fabb..f9d65ed 100644
--- a/modules/m4.c
+++ b/modules/m4.c
@@ -178,13 +178,14 @@ M4BUILTIN_HANDLER (define)
 
 M4BUILTIN_HANDLER (undefine)
 {
+  const char *me = M4ARG (0);
   unsigned int i;
   for (i = 1; i < argc; i++)
     {
       const char *name = M4ARG (i);
 
       if (!m4_symbol_lookup (M4SYMTAB, name))
-	m4_warn (context, 0, M4ARG (0), _("undefined macro `%s'"), name);
+	m4_warn (context, 0, me, _("undefined macro `%s'"), name);
       else
 	m4_symbol_delete (M4SYMTAB, name);
     }
@@ -209,13 +210,14 @@ M4BUILTIN_HANDLER (pushdef)
 
 M4BUILTIN_HANDLER (popdef)
 {
+  const char *me = M4ARG (0);
   unsigned int i;
   for (i = 1; i < argc; i++)
     {
       const char *name = M4ARG (i);
 
       if (!m4_symbol_lookup (M4SYMTAB, name))
-	m4_warn (context, 0, M4ARG (0), _("undefined macro `%s'"), name);
+	m4_warn (context, 0, me, _("undefined macro `%s'"), name);
       else
 	m4_symbol_popdef (M4SYMTAB, name);
     }
@@ -240,10 +242,7 @@ M4BUILTIN_HANDLER (ifelse)
 
   /* The valid ranges of argc for ifelse is discontinuous, we cannot
      rely on the regular mechanisms.  */
-  if (argc == 2)
-    return;
-
-  if (m4_bad_argc (context, argc, me, 3, -1, false))
+  if (argc == 2 || m4_bad_argc (context, argc, me, 3, -1, false))
     return;
   else if (argc % 3 == 0)
     /* Diagnose excess arguments if 5, 8, 11, etc., actual arguments.  */
@@ -254,7 +253,7 @@ M4BUILTIN_HANDLER (ifelse)
 
   while (1)
     {
-      if (strcmp (M4ARG (index), M4ARG (index + 1)) == 0)
+      if (m4_arg_equal (argv, index, index + 1))
 	{
 	  obstack_grow (obs, M4ARG (index + 2), m4_arg_len (argv, index + 2));
 	  return;
@@ -317,6 +316,7 @@ void
 m4_dump_symbols (m4 *context, m4_dump_symbol_data *data, unsigned int argc,
 		 m4_macro_args *argv, bool complain)
 {
+  const char *me = M4ARG (0);
   assert (obstack_object_size (data->obs) == 0);
   data->size = obstack_room (data->obs) / sizeof (const char *);
 
@@ -329,12 +329,12 @@ m4_dump_symbols (m4 *context, m4_dump_symbol_data *data, unsigned int argc,
 
       for (i = 1; i < argc; i++)
 	{
-	  symbol = m4_symbol_lookup (M4SYMTAB, M4ARG (i));
+	  const char *name = M4ARG (i);
+	  symbol = m4_symbol_lookup (M4SYMTAB, name);
 	  if (symbol != NULL)
-	    dump_symbol_CB (NULL, M4ARG (i), symbol, data);
+	    dump_symbol_CB (NULL, name, symbol, data);
 	  else if (complain)
-	    m4_warn (context, 0, M4ARG (0), _("undefined macro `%s'"),
-		     M4ARG (i));
+	    m4_warn (context, 0, me, _("undefined macro `%s'"), name);
 	}
     }
 
@@ -508,14 +508,14 @@ m4_sysval_flush (m4 *context, bool report)
 
 M4BUILTIN_HANDLER (syscmd)
 {
-   if (m4_get_safer_opt (context))
-   {
-     m4_error (context, 0, 0, M4ARG (0), _("disabled by --safer"));
-     return;
-   }
-
-   /* Optimize the empty command.  */
-  if (*M4ARG (1) == '\0')
+  if (m4_get_safer_opt (context))
+    {
+      m4_error (context, 0, 0, M4ARG (0), _("disabled by --safer"));
+      return;
+    }
+
+  /* Optimize the empty command.  */
+  if (m4_arg_empty (argv, 1))
     {
       m4_set_sysval (0);
       return;
-- 
1.5.3.5

From 9db4aab8ae052e044e1e5b024421d75e91f45b92 Mon Sep 17 00:00:00 2001
From: Eric Blake <[email protected]>
Date: Fri, 19 Oct 2007 21:45:38 -0600
Subject: [PATCH] Stage 4: route indir, builtin through ref; make argv opaque.

* src/m4.h (obstack_regrow): Borrow definition from head.
(struct token_chain): Add flatten and len members.
(arg_equal, arg_empty, make_argv_ref): New prototypes.
(struct macro_arguments): Move...
* src/macro.c (struct macro_arguments): ...here, making it
opaque.  Add has_ref member.
(empty_token): New placeholder, for optimizing comparison with
empty string.
(collect_arguments): Change signature, and populate new fields.
(expand_macro): Alter handling of obstacks.
(arg_token): New helper method.
(arg_equal, arg_empty, make_argv_ref): New methods.
(arg_type, arg_text, arg_len, arg_func): Use new methods.
* src/builtin.c (m4_ifelse, m4_builtin, m4_indir, m4_eval):
Likewise.
* src/format.c (format): Likewise.

(cherry picked from commit ab7d5ea40dd30e38cdafdfa69e868390ff6f72ab)

Signed-off-by: Eric Blake <[email protected]>
---
 ChangeLog     |   18 +++
 src/builtin.c |   88 +++------------
 src/format.c  |    2 +-
 src/m4.h      |   37 +++----
 src/macro.c   |  326 ++++++++++++++++++++++++++++++++++++++++++++++-----------
 5 files changed, 317 insertions(+), 154 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 7cd6fc8..0662337 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,23 @@
 2007-11-29  Eric Blake  <[email protected]>
 
+	Stage 4: route indir, builtin through ref; make argv opaque.
+	* src/m4.h (obstack_regrow): Borrow definition from head.
+	(struct token_chain): Add flatten and len members.
+	(arg_equal, arg_empty, make_argv_ref): New prototypes.
+	(struct macro_arguments): Move...
+	* src/macro.c (struct macro_arguments): ...here, making it
+	opaque.  Add has_ref member.
+	(empty_token): New placeholder, for optimizing comparison with
+	empty string.
+	(collect_arguments): Change signature, and populate new fields.
+	(expand_macro): Alter handling of obstacks.
+	(arg_token): New helper method.
+	(arg_equal, arg_empty, make_argv_ref): New methods.
+	(arg_type, arg_text, arg_len, arg_func): Use new methods.
+	* src/builtin.c (m4_ifelse, m4_builtin, m4_indir, m4_eval):
+	Likewise.
+	* src/format.c (format): Likewise.
+
 	Stage 3: cache length, rather than computing it.
 	* src/input.c (next_token): Grab length from obstack rather than
 	calling strlen.
diff --git a/src/builtin.c b/src/builtin.c
index e719cdd..b4053f1 100644
--- a/src/builtin.c
+++ b/src/builtin.c
@@ -758,16 +758,10 @@ m4_ifdef (struct obstack *obs, int argc, macro_arguments *argv)
 static void
 m4_ifelse (struct obstack *obs, int argc, macro_arguments *argv)
 {
-  const char *result;
-  const char *me;
+  const char *me = ARG (0);
   int index;
-  size_t len = 0;
-
-  if (argc == 2)
-    return;
 
-  me = ARG (0);
-  if (bad_argc (me, argc, 3, -1))
+  if (argc == 2 || bad_argc (me, argc, 3, -1))
     return;
   else if (argc % 3 == 0)
     /* Diagnose excess arguments if 5, 8, 11, etc., actual arguments.  */
@@ -776,17 +770,13 @@ m4_ifelse (struct obstack *obs, int argc, macro_arguments *argv)
   index = 1;
   argc--;
 
-  result = NULL;
-  while (result == NULL)
-
-    if (arg_len (argv, index) == arg_len (argv, index + 1)
-	&& strcmp (ARG (index), ARG (index + 1)) == 0)
-      {
-	result = ARG (index + 2);
-	len = arg_len (argv, index + 2);
-      }
-
-    else
+  while (true)
+    {
+      if (arg_equal (argv, index, index + 1))
+	{
+	  obstack_grow (obs, ARG (index + 2), arg_len (argv, index + 2));
+	  return;
+	}
       switch (argc)
 	{
 	case 3:
@@ -794,16 +784,14 @@ m4_ifelse (struct obstack *obs, int argc, macro_arguments *argv)
 
 	case 4:
 	case 5:
-	  result = ARG (index + 3);
-	  len = arg_len (argv, index + 3);
-	  break;
+	  obstack_grow (obs, ARG (index + 3), arg_len (argv, index + 3));
+	  return;
 
 	default:
 	  argc -= 3;
 	  index += 3;
 	}
-
-  obstack_grow (obs, result, len);
+    }
 }
 
 /*---------------------------------------------------------------------.
@@ -944,29 +932,9 @@ m4_builtin (struct obstack *obs, int argc, macro_arguments *argv)
     m4_warn (0, me, _("undefined builtin `%s'"), name);
   else
     {
-      int i;
-      /* TODO make use of $@ reference, instead of copying argv.  */
-      /* TODO make accessor in macro.c that performs this
-	 construction, so that argv can be opaque type.  */
-      macro_arguments *new_argv = xmalloc (offsetof (macro_arguments, array)
-					   + ((argc - 2)
-					      * sizeof (token_data *)));
-      new_argv->argc = argc - 1;
-      new_argv->inuse = false;
-      new_argv->argv0 = name;
-      new_argv->argv0_len = arg_len (argv, 1);
-      new_argv->arraylen = argc - 2;
-      memcpy (&new_argv->array[0], &argv->array[1],
-	      (argc - 2) * sizeof (token_data *));
-      if (!bp->groks_macro_args)
-	for (i = 2; i < argc; i++)
-	  if (arg_type (argv, i) != TOKEN_TEXT)
-	    {
-	      TOKEN_DATA_TYPE (new_argv->array[i - 2]) = TOKEN_TEXT;
-	      TOKEN_DATA_TEXT (new_argv->array[i - 2]) = (char *) "";
-	    }
+      macro_arguments *new_argv = make_argv_ref (argv, name, arg_len (argv, 1),
+						 true, !bp->groks_macro_args);
       bp->func (obs, argc - 1, new_argv);
-      free (new_argv);
     }
 }
 
@@ -998,29 +966,9 @@ m4_indir (struct obstack *obs, int argc, macro_arguments *argv)
     m4_warn (0, me, _("undefined macro `%s'"), name);
   else
     {
-      int i;
-      /* TODO make use of $@ reference, instead of copying argv.  */
-      /* TODO make accessor in macro.c that performs this
-	 construction, so that argv can be opaque type.  */
-      macro_arguments *new_argv = xmalloc (offsetof (macro_arguments, array)
-					   + ((argc - 2)
-					      * sizeof (token_data *)));
-      new_argv->argc = argc - 1;
-      new_argv->inuse = false;
-      new_argv->argv0 = name;
-      new_argv->argv0_len = arg_len (argv, 1);
-      new_argv->arraylen = argc - 2;
-      memcpy (&new_argv->array[0], &argv->array[1],
-	      (argc - 2) * sizeof (token_data *));
-      if (!SYMBOL_MACRO_ARGS (s))
-	for (i = 2; i < argc; i++)
-	  if (arg_type (argv, i) != TOKEN_TEXT)
-	    {
-	      TOKEN_DATA_TYPE (new_argv->array[i - 2]) = TOKEN_TEXT;
-	      TOKEN_DATA_TEXT (new_argv->array[i - 2]) = (char *) "";
-	    }
+      macro_arguments *new_argv = make_argv_ref (argv, name, arg_len (argv, 1),
+						 true, !SYMBOL_MACRO_ARGS (s));
       call_macro (s, argc - 1, new_argv, obs);
-      free (new_argv);
     }
 }
 
@@ -1191,7 +1139,7 @@ m4_eval (struct obstack *obs, int argc, macro_arguments *argv)
   if (bad_argc (me, argc, 1, 3))
     return;
 
-  if (*ARG (2) && !numeric_arg (me, ARG (2), &radix))
+  if (!arg_empty (argv, 2) && !numeric_arg (me, ARG (2), &radix))
     return;
 
   if (radix < 1 || radix > 36)
@@ -1208,7 +1156,7 @@ m4_eval (struct obstack *obs, int argc, macro_arguments *argv)
       return;
     }
 
-  if (!*ARG (1))
+  if (arg_empty (argv, 1))
     m4_warn (0, me, _("empty string treated as 0"));
   else if (evaluate (me, ARG (1), &value))
     return;
diff --git a/src/format.c b/src/format.c
index 7fc8fb1..20b3e28 100644
--- a/src/format.c
+++ b/src/format.c
@@ -51,7 +51,7 @@
 void
 format (struct obstack *obs, int argc, macro_arguments *argv)
 {
-  const char *me = argv->argv0;
+  const char *me = arg_text (argv, 0);
   const char *f;			/* format control string */
   const char *fmt;			/* position within f */
   char fstart[] = "%'+- 0#*.*hhd";	/* current format spec */
diff --git a/src/m4.h b/src/m4.h
index 3a6acc3..ac81998 100644
--- a/src/m4.h
+++ b/src/m4.h
@@ -87,7 +87,15 @@ typedef struct string STRING;
 #define obstack_chunk_alloc	xmalloc
 #define obstack_chunk_free	free
 
-/* Those must come first.  */
+/* glibc's obstack left out the ability to suspend and resume growth
+   of an object on the stack.  Reopen OBJECT (previously returned by
+   obstack_alloc or obstack_finish) with SIZE for additional growth,
+   freeing all objects that occur later in the stack.  */
+#define obstack_regrow(OBS, OBJECT, SIZE)                \
+  (obstack_free (OBS, (char *) (OBJECT) + (SIZE)),       \
+   (OBS)->object_base = (char *) (OBJECT))
+
+/* These must come first.  */
 typedef struct token_data token_data;
 typedef struct macro_arguments macro_arguments;
 typedef void builtin_func (struct obstack *, int, macro_arguments *);
@@ -272,8 +280,10 @@ struct token_chain
 {
   token_chain *next;	/* Pointer to next link of chain.  */
   char *str;		/* NUL-terminated string if text, else NULL.  */
+  size_t len;		/* Length of str, else 0.  */
   macro_arguments *argv;/* Reference to earlier $@.  */
   unsigned int index;	/* Argument index within argv.  */
+  bool flatten;		/* True to treat builtins as text.  */
 };
 
 /* The content of a token or macro argument.  */
@@ -303,27 +313,6 @@ struct token_data
   u;
 };
 
-/* TODO - make this struct opaque, and move definition to macro.c.  */
-/* Opaque structure describing all arguments to a macro, including the
-   macro name at index 0.  */
-struct macro_arguments
-{
-  /* Number of arguments owned by this object, may be larger than
-     arraylen since the array can refer to multiple arguments via a
-     single $@ reference.  */
-  unsigned int argc;
-  /* False unless the macro expansion refers to $@, determines whether
-     this object can be freed at end of macro expansion or must wait
-     until next byte read from file.  */
-  bool inuse;
-  const char *argv0; /* The macro name being expanded.  */
-  size_t argv0_len; /* Length of argv0.  */
-  size_t arraylen; /* True length of allocated elements in array.  */
-  /* Used as a variable-length array, storing information about each
-     argument.  */
-  token_data *array[FLEXIBLE_ARRAY_MEMBER];
-};
-
 #define TOKEN_DATA_TYPE(Td)		((Td)->type)
 #define TOKEN_DATA_LEN(Td)		((Td)->u.u_t.len)
 #define TOKEN_DATA_TEXT(Td)		((Td)->u.u_t.text)
@@ -442,8 +431,12 @@ void call_macro (symbol *, int, macro_arguments *, struct obstack *);
 unsigned int arg_argc (macro_arguments *);
 token_data_type arg_type (macro_arguments *, unsigned int);
 const char *arg_text (macro_arguments *, unsigned int);
+bool arg_equal (macro_arguments *, unsigned int, unsigned int);
+bool arg_empty (macro_arguments *, unsigned int);
 size_t arg_len (macro_arguments *, unsigned int);
 builtin_func *arg_func (macro_arguments *, unsigned int);
+macro_arguments *make_argv_ref (macro_arguments *, const char *, size_t,
+				bool, bool);
 
 
 /* File: builtin.c  --- builtins.  */
diff --git a/src/macro.c b/src/macro.c
index 320727d..e257485 100644
--- a/src/macro.c
+++ b/src/macro.c
@@ -24,6 +24,29 @@
 
 #include "m4.h"
 
+/* Opaque structure describing all arguments to a macro, including the
+   macro name at index 0.  */
+struct macro_arguments
+{
+  /* Number of arguments owned by this object, may be larger than
+     arraylen since the array can refer to multiple arguments via a
+     single $@ reference.  */
+  unsigned int argc;
+  /* False unless the macro expansion refers to $@, determines whether
+     this object can be freed at end of macro expansion or must wait
+     until next byte read from file.  */
+  bool_bitfield inuse : 1;
+  /* False if all arguments are just text or func, true if this argv
+     refers to another one.  */
+  bool_bitfield has_ref : 1;
+  const char *argv0; /* The macro name being expanded.  */
+  size_t argv0_len; /* Length of argv0.  */
+  size_t arraylen; /* True length of allocated elements in array.  */
+  /* Used as a variable-length array, storing information about each
+     argument.  */
+  token_data *array[FLEXIBLE_ARRAY_MEMBER];
+};
+
 static void expand_macro (symbol *);
 static void expand_token (struct obstack *, token_type, token_data *, int);
 
@@ -35,24 +58,24 @@ static int macro_call_id = 0;
 
 /* The shared stack of collected arguments for macro calls; as each
    argument is collected, it is finished and its location stored in
-   argv_stack.  Normally, this stack can be used simultaneously by
-   multiple macro calls; the exception is when an outer macro has
-   generated some text, then calls a nested macro, in which case the
-   nested macro must use a local stack to leave the unfinished text
-   alone.  Too bad obstack.h does not provide an easy way to reopen a
-   finished object for further growth, but in practice this does not
-   hurt us too much.  */
+   argv_stack.  This stack can be used simultaneously by multiple
+   macro calls, using obstack_regrow to handle partial objects
+   embedded in the stack.  */
 static struct obstack argc_stack;
 
 /* The shared stack of pointers to collected arguments for macro
-   calls.  This object is never finished; we exploit the fact that
-   obstack_blank is documented to take a negative size to reduce the
-   size again.  */
+   calls.  This stack can be used simultaneously by multiple macro
+   calls, using obstack_regrow to handle partial objects embedded in
+   the stack.  */
 static struct obstack argv_stack;
 
-/*----------------------------------------------------------------------.
-| This function read all input, and expands each token, one at a time.  |
-`----------------------------------------------------------------------*/
+/* The empty string token.  */
+static token_data empty_token;
+
+/*----------------------------------------------------------------.
+| This function reads all input, and expands each token, one at a |
+| time.                                                           |
+`----------------------------------------------------------------*/
 
 void
 expand_input (void)
@@ -64,6 +87,13 @@ expand_input (void)
   obstack_init (&argc_stack);
   obstack_init (&argv_stack);
 
+  TOKEN_DATA_TYPE (&empty_token) = TOKEN_TEXT;
+  TOKEN_DATA_TEXT (&empty_token) = "";
+  TOKEN_DATA_LEN (&empty_token) = 0;
+#ifdef ENABLE_CHANGEWORD
+  TOKEN_DATA_ORIG_TEXT (&empty_token) = "";
+#endif
+
   while ((t = next_token (&td, &line, NULL)) != TOKEN_EOF)
     expand_token ((struct obstack *) NULL, t, &td, line);
 
@@ -237,12 +267,11 @@ expand_argument (struct obstack *obs, token_data *argp, const char *caller)
 /*-------------------------------------------------------------------------.
 | Collect all the arguments to a call of the macro SYM.  The arguments are |
 | stored on the obstack ARGUMENTS and a table of pointers to the arguments |
-| on the obstack ARGPTR.						   |
+| on the obstack argv_stack.						   |
 `-------------------------------------------------------------------------*/
 
 static macro_arguments *
-collect_arguments (symbol *sym, struct obstack *argptr, unsigned int argv_base,
-		   struct obstack *arguments)
+collect_arguments (symbol *sym, struct obstack *arguments)
 {
   token_data td;
   token_data *tdp;
@@ -253,10 +282,11 @@ collect_arguments (symbol *sym, struct obstack *argptr, unsigned int argv_base,
 
   args.argc = 1;
   args.inuse = false;
+  args.has_ref = false;
   args.argv0 = SYMBOL_NAME (sym);
   args.argv0_len = strlen (args.argv0);
   args.arraylen = 0;
-  obstack_grow (argptr, &args, offsetof (macro_arguments, array));
+  obstack_grow (&argv_stack, &args, offsetof (macro_arguments, array));
 
   if (peek_token () == TOKEN_OPEN)
     {
@@ -265,20 +295,18 @@ collect_arguments (symbol *sym, struct obstack *argptr, unsigned int argv_base,
 	{
 	  more_args = expand_argument (arguments, &td, SYMBOL_NAME (sym));
 
-	  if (!groks_macro_args && TOKEN_DATA_TYPE (&td) == TOKEN_FUNC)
-	    {
-	      TOKEN_DATA_TYPE (&td) = TOKEN_TEXT;
-	      TOKEN_DATA_TEXT (&td) = (char *) "";
-	      TOKEN_DATA_LEN (&td) = 0;
-	    }
-	  tdp = (token_data *) obstack_copy (arguments, &td, sizeof td);
-	  obstack_ptr_grow (argptr, tdp);
+	  if ((TOKEN_DATA_TYPE (&td) == TOKEN_TEXT && !TOKEN_DATA_LEN (&td))
+	      || (!groks_macro_args && TOKEN_DATA_TYPE (&td) == TOKEN_FUNC))
+	    tdp = &empty_token;
+	  else
+	    tdp = (token_data *) obstack_copy (arguments, &td, sizeof td);
+	  obstack_ptr_grow (&argv_stack, tdp);
 	  args.arraylen++;
 	  args.argc++;
 	}
       while (more_args);
     }
-  argv = (macro_arguments *) ((char *) obstack_base (argptr) + argv_base);
+  argv = (macro_arguments *) obstack_finish (&argv_stack);
   argv->argc = args.argc;
   argv->arraylen = args.arraylen;
   return argv;
@@ -327,11 +355,11 @@ call_macro (symbol *sym, int argc, macro_arguments *argv,
 static void
 expand_macro (symbol *sym)
 {
-  struct obstack arguments;	/* Alternate obstack if argc_stack is busy.  */
-  unsigned int argv_base;	/* Size of argv_stack on entry.  */
-  void *argc_start;		/* Start of argc_stack, else NULL if unsafe.  */
+  void *argc_base = NULL;	/* Base of argc_stack on entry.  */
+  void *argv_base = NULL;	/* Base of argv_stack on entry.  */
+  unsigned int argc_size;	/* Size of argc_stack on entry.  */
+  unsigned int argv_size;	/* Size of argv_stack on entry.  */
   macro_arguments *argv;
-  int argc;
   struct obstack *expansion;
   const char *expanded;
   bool traced;
@@ -360,24 +388,16 @@ expand_macro (symbol *sym)
 
   traced = (debug_level & DEBUG_TRACE_ALL) || SYMBOL_TRACED (sym);
 
-  argv_base = obstack_object_size (&argv_stack);
-  if (obstack_object_size (&argc_stack) > 0)
-    {
-      /* We cannot use argc_stack if this is a nested invocation, and an
-	 outer invocation has an unfinished argument being
-	 collected.  */
-      obstack_init (&arguments);
-      argc_start = NULL;
-    }
-  else
-    argc_start = obstack_finish (&argc_stack);
+  argc_size = obstack_object_size (&argc_stack);
+  argv_size = obstack_object_size (&argv_stack);
+  argc_base = obstack_finish (&argc_stack);
+  if (0 < argv_size)
+    argv_base = obstack_finish (&argv_stack);
 
   if (traced && (debug_level & DEBUG_TRACE_CALL))
     trace_prepre (SYMBOL_NAME (sym), my_call_id);
 
-  argv = collect_arguments (sym, &argv_stack, argv_base,
-			    argc_start ? &argc_stack : &arguments);
-  argc = argv->argc;
+  argv = collect_arguments (sym, &argc_stack);
 
   loc_close_file = current_file;
   loc_close_line = current_line;
@@ -385,14 +405,14 @@ expand_macro (symbol *sym)
   current_line = loc_open_line;
 
   if (traced)
-    trace_pre (SYMBOL_NAME (sym), my_call_id, argc, argv);
+    trace_pre (SYMBOL_NAME (sym), my_call_id, argv->argc, argv);
 
   expansion = push_string_init ();
-  call_macro (sym, argc, argv, expansion);
+  call_macro (sym, argv->argc, argv, expansion);
   expanded = push_string_finish ();
 
   if (traced)
-    trace_post (SYMBOL_NAME (sym), my_call_id, argc, argv, expanded);
+    trace_post (SYMBOL_NAME (sym), my_call_id, argv->argc, argv, expanded);
 
   current_file = loc_close_file;
   current_line = loc_close_line;
@@ -404,11 +424,50 @@ expand_macro (symbol *sym)
     free_symbol (sym);
 
   /* TODO pay attention to argv->inuse, in case someone is depending on $@.  */
-  if (argc_start)
-    obstack_free (&argc_stack, argc_start);
+  if (0 < argc_size)
+    obstack_regrow (&argc_stack, argc_base, argc_size);
+  else
+    obstack_free (&argc_stack, argc_base);
+  if (0 < argv_size)
+    obstack_regrow (&argv_stack, argv_base, argv_size);
   else
-    obstack_free (&arguments, NULL);
-  obstack_blank (&argv_stack, argv_base - obstack_object_size (&argv_stack));
+    obstack_free (&argv_stack, argv);
+}
+
+/* Given ARGV, return the token_data that contains argument INDEX;
+   INDEX must be > 0, < argv->argc.  */
+static token_data *
+arg_token (macro_arguments *argv, unsigned int index)
+{
+  unsigned int i;
+  token_data *token;
+
+  assert (index && index < argv->argc);
+  if (!argv->has_ref)
+    return argv->array[index - 1];
+  /* Must cycle through all tokens, until we find index, since a ref
+     may occupy multiple indices.  */
+  for (i = 0; i < argv->arraylen; i++)
+    {
+      token = argv->array[i];
+      if (TOKEN_DATA_TYPE (token) == TOKEN_COMP)
+	{
+	  token_chain *chain = token->u.chain;
+	  /* TODO - for now we support only a single-length $@ chain.  */
+	  assert (!chain->next && !chain->str);
+	  if (index < chain->argv->argc - (chain->index - 1))
+	    {
+	      token = arg_token (chain->argv, chain->index - 1 + index);
+	      if (chain->flatten && TOKEN_DATA_TYPE (token) == TOKEN_FUNC)
+		token = &empty_token;
+	      break;
+	    }
+	  index -= chain->argv->argc - chain->index;
+	}
+      else if (--index == 0)
+	break;
+    }
+  return token;
 }
 
 
@@ -424,9 +483,15 @@ arg_argc (macro_arguments *argv)
 token_data_type
 arg_type (macro_arguments *argv, unsigned int index)
 {
+  token_data_type type;
+  token_data *token;
+
   if (index == 0 || index >= argv->argc)
     return TOKEN_TEXT;
-  return TOKEN_DATA_TYPE (argv->array[index - 1]);
+  token = arg_token (argv, index);
+  type = TOKEN_DATA_TYPE (token);
+  assert (type != TOKEN_COMP);
+  return type;
 }
 
 /* Given ARGV, return the text at argument INDEX, or NULL if the
@@ -435,13 +500,59 @@ arg_type (macro_arguments *argv, unsigned int index)
 const char *
 arg_text (macro_arguments *argv, unsigned int index)
 {
+  token_data *token;
+
   if (index == 0)
     return argv->argv0;
   if (index >= argv->argc)
     return "";
-  if (TOKEN_DATA_TYPE (argv->array[index - 1]) != TOKEN_TEXT)
-    return NULL;
-  return TOKEN_DATA_TEXT (argv->array[index - 1]);
+  token = arg_token (argv, index);
+  switch (TOKEN_DATA_TYPE (token))
+    {
+    case TOKEN_TEXT:
+      return TOKEN_DATA_TEXT (token);
+    case TOKEN_FUNC:
+      return NULL;
+    case TOKEN_COMP:
+      /* TODO - how to concatenate multiple arguments?  For now, we expect
+	 only one element in the chain, and arg_token dereferences it.  */
+    default:
+      break;
+    }
+  assert (!"arg_text");
+  abort ();
+}
+
+/* Given ARGV, compare text arguments INDEXA and INDEXB for equality.
+   Both indices must be non-zero and less than argc.  Return true if
+   the arguments contain the same contents; often more efficient than
+   strcmp (arg_text (argv, indexa), arg_text (argv, indexb)) == 0.  */
+bool
+arg_equal (macro_arguments *argv, unsigned int indexa, unsigned int indexb)
+{
+  token_data *ta = arg_token (argv, indexa);
+  token_data *tb = arg_token (argv, indexb);
+
+  if (ta == &empty_token || tb == &empty_token)
+    return ta == tb;
+  /* TODO - allow builtin tokens in the comparison?  */
+  assert (TOKEN_DATA_TYPE (ta) == TOKEN_TEXT
+	  && TOKEN_DATA_TYPE (tb) == TOKEN_TEXT);
+  return (TOKEN_DATA_LEN (ta) == TOKEN_DATA_LEN (tb)
+	  && strcmp (TOKEN_DATA_TEXT (ta), TOKEN_DATA_TEXT (tb)) == 0);
+}
+
+/* Given ARGV, return true if argument INDEX is the empty string.
+   This gives the same result as comparing arg_len against 0, but is
+   often faster.  */
+bool
+arg_empty (macro_arguments *argv, unsigned int index)
+{
+  if (index == 0)
+    return argv->argv0_len == 0;
+  if (index >= argv->argc)
+    return true;
+  return arg_token (argv, index) == &empty_token;
 }
 
 /* Given ARGV, return the length of argument INDEX, or SIZE_MAX if the
@@ -449,13 +560,28 @@ arg_text (macro_arguments *argv, unsigned int index)
 size_t
 arg_len (macro_arguments *argv, unsigned int index)
 {
+  token_data *token;
+
   if (index == 0)
     return argv->argv0_len;
   if (index >= argv->argc)
     return 0;
-  if (TOKEN_DATA_TYPE (argv->array[index - 1]) != TOKEN_TEXT)
-    return SIZE_MAX;
-  return TOKEN_DATA_LEN (argv->array[index - 1]);
+  token = arg_token (argv, index);
+  switch (TOKEN_DATA_TYPE (token))
+    {
+    case TOKEN_TEXT:
+      assert ((token == &empty_token) == (TOKEN_DATA_LEN (token) == 0));
+      return TOKEN_DATA_LEN (token);
+    case TOKEN_FUNC:
+      return SIZE_MAX;
+    case TOKEN_COMP:
+      /* TODO - how to concatenate multiple arguments?  For now, we expect
+	 only one element in the chain, and arg_token dereferences it.  */
+    default:
+      break;
+    }
+  assert (!"arg_len");
+  abort ();
 }
 
 /* Given ARGV, return the builtin function referenced by argument
@@ -464,8 +590,86 @@ arg_len (macro_arguments *argv, unsigned int index)
 builtin_func *
 arg_func (macro_arguments *argv, unsigned int index)
 {
-  if (index == 0 || index >= argv->argc
-      || TOKEN_DATA_TYPE (argv->array[index - 1]) != TOKEN_FUNC)
+  token_data *token;
+
+  if (index == 0 || index >= argv->argc)
     return NULL;
-  return TOKEN_DATA_FUNC (argv->array[index - 1]);
+  token = arg_token (argv, index);
+  switch (TOKEN_DATA_TYPE (token))
+    {
+    case TOKEN_FUNC:
+      return TOKEN_DATA_FUNC (token);
+    case TOKEN_TEXT:
+      return NULL;
+    case TOKEN_COMP:
+      /* TODO - how to concatenate multiple arguments?  For now, we expect
+	 only one element in the chain.  */
+    default:
+      break;
+    }
+  assert(!"arg_func");
+  abort ();
+}
+
+/* Create a new argument object using the same obstack as ARGV; thus,
+   the new object will automatically be freed when the original is
+   freed.  Explicitly set the macro name (argv[0]) from ARGV0 with
+   length ARGV0_LEN.  If SKIP, set argv[1] of the new object to
+   argv[2] of the old, otherwise the objects share all arguments.  If
+   FLATTEN, any non-text in ARGV is flattened to an empty string when
+   referenced through the new object.  */
+macro_arguments *
+make_argv_ref (macro_arguments *argv, const char *argv0, size_t argv0_len,
+	       bool skip, bool flatten)
+{
+  macro_arguments *new_argv;
+  token_data *token;
+  token_chain *chain;
+  unsigned int index = skip ? 2 : 1;
+
+  assert (obstack_object_size (&argv_stack) == 0);
+  /* When making a reference through a reference, point to the
+     original if possible.  */
+  if (argv->has_ref)
+    {
+      /* TODO - for now we support only a single-length $@ chain.  */
+      assert (argv->arraylen == 1
+	      && TOKEN_DATA_TYPE (argv->array[0]) == TOKEN_COMP);
+      chain = argv->array[0]->u.chain;
+      assert (!chain->next && !chain->str);
+      argv = chain->argv;
+      index += chain->index - 1;
+    }
+  if (argv->argc <= index)
+    {
+      new_argv = (macro_arguments *)
+	obstack_alloc (&argv_stack, offsetof (macro_arguments, array));
+      new_argv->arraylen = 0;
+      new_argv->has_ref = false;
+    }
+  else
+    {
+      new_argv = (macro_arguments *)
+	obstack_alloc (&argv_stack,
+		       offsetof (macro_arguments, array) + sizeof token);
+      token = (token_data *) obstack_alloc (&argv_stack, sizeof *token);
+      chain = (token_chain *) obstack_alloc (&argv_stack, sizeof *chain);
+      new_argv->arraylen = 1;
+      new_argv->array[0] = token;
+      new_argv->has_ref = true;
+      TOKEN_DATA_TYPE (token) = TOKEN_COMP;
+      token->u.chain = chain;
+      chain->next = NULL;
+      chain->str = NULL;
+      chain->len = 0;
+      chain->argv = argv;
+      chain->index = index;
+      chain->flatten = flatten;
+    }
+  /* TODO - should argv->inuse be set?  */
+  new_argv->argc = argv->argc - (index - 1);
+  new_argv->inuse = false;
+  new_argv->argv0 = argv0;
+  new_argv->argv0_len = argv0_len;
+  return new_argv;
 }
-- 
1.5.3.5
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.