documentation ideas from autoconf

Eric Blake <[email protected]>
Newsgroups gmane.comp.gnu.m4.patches
Message-ID <[email protected]>
I'm committing this to branch-1.6 and master; it documents some slick m4 tricks 
learned recently in autoconf's m4sugar.

From cacb2125cc8d3e14dfdcc24260d2daf2a7684640 Mon Sep 17 00:00:00 2001
From: Eric Blake <[email protected]>
Date: Tue, 4 Nov 2008 15:00:32 -0700
Subject: [PATCH] Document copy composite using stack_foreach and curry.

* doc/m4.texinfo (Stacks): New node, to document pushdef stack
manipulation.
(Ifelse): Move define_blind...
(Composition): ...to this new node.  Document currying, then use
it to implement copy and rename.
* examples/curry.m4: New file.
* examples/stack.m4: Likewise.
* examples/Makefile.am (EXTRA_DIST): Distribute them.

Signed-off-by: Eric Blake <[email protected]>
---
 ChangeLog            |   12 ++
 doc/m4.texinfo       |  314 +++++++++++++++++++++++++++++++++++++++++---------
 examples/Makefile.am |    2 +
 examples/curry.m4    |    7 +
 examples/stack.m4    |   16 +++
 5 files changed, 294 insertions(+), 57 deletions(-)
 create mode 100644 examples/curry.m4
 create mode 100644 examples/stack.m4

diff --git a/ChangeLog b/ChangeLog
index cf0ffc0..a0ca8e8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+2008-11-26  Eric Blake  <[email protected]>
+
+	Document copy composite using stack_foreach and curry.
+	* doc/m4.texinfo (Stacks): New node, to document pushdef stack
+	manipulation.
+	(Ifelse): Move define_blind...
+	(Composition): ...to this new node.  Document currying, then use
+	it to implement copy and rename.
+	* examples/curry.m4: New file.
+	* examples/stack.m4: Likewise.
+	* examples/Makefile.am (EXTRA_DIST): Distribute them.
+
 2008-11-04  Eric Blake  <[email protected]>
 
 	Upgrade to FDL 1.3.
diff --git a/doc/m4.texinfo b/doc/m4.texinfo
index 9fcbddd..152dffd 100644
--- a/doc/m4.texinfo
+++ b/doc/m4.texinfo
@@ -197,6 +197,8 @@ Top
 * Shift::                       Recursion in @code{m4}
 * Forloop::                     Iteration by counting
 * Foreach::                     Iteration by list contents
+* Stacks::                      Working with definition stacks
+* Composition::                 Building macros with macros
 
 How to debug macros and input
 
@@ -2486,6 +2488,7 @@ Pushdef
 @cindex temporary redefinition of macros
 @cindex redefinition of macros, temporary
 @cindex definition stack
+@cindex pushdef stack
 @cindex stack, macro definition
 It is possible to redefine a macro temporarily, reverting to the
 previous definition at a later time.  This is done with the builtins
@@ -2833,6 +2836,8 @@ Conditionals
 * Shift::                       Recursion in @code{m4}
 * Forloop::                     Iteration by counting
 * Foreach::                     Iteration by list contents
+* Stacks::                      Working with definition stacks
+* Composition::                 Building macros with macros
 @end menu
 
 @node Ifdef
@@ -2956,67 +2961,13 @@ Ifelse
 @result{}arguments:3
 @end example
 
-Since m4 is a macro language, it is even possible to write a macro that
-makes defining blind macros easier:
-
-@deffn Composite define_blind (@var{name}, @ovar{value})
-Defines @var{name} as a blind macro, such that @var{name} will expand to
-@var{value} only when given explicit arguments.  @var{value} should not
-be the result of @code{defn} (@pxref{Defn}).  This macro is only
-recognized with parameters, and results in an empty string.
-@end deffn
-
-Defining a macro to define another macro can be a bit tricky.  We want
-to use a literal @samp{$#} in the argument to the nested @code{define}.
-However, if @samp{$} and @samp{#} are adjacent in the definition of
-@code{define_blind}, then it would be expanded as the number of
-arguments to @code{define_blind} rather than the intended number of
-arguments to @var{name}.  The solution is to pass the difficult
-characters through extra arguments to a helper macro
-@code{_define_blind}.
-
-As for the limitation against using @code{defn}, there are two reasons.
-If a macro was previously defined with @code{define_blind}, then it can
-safely be renamed to a new blind macro using plain @code{define}; using
-@code{define_blind} to rename it just adds another layer of
-@code{ifelse}, occupying memory and slowing down execution.  And if a
-macro is a builtin, then it would result in an attempt to define a macro
-consisting of both text and a builtin token; this is not supported, and
-the builtin token is flattened to an empty string.
-
-With that explanation, here's the definition, and some sample usage.
-Notice that @code{define_blind} is itself a blind macro.
-
-@example
-$ @kbd{m4 -d}
-define(`define_blind', `ifelse(`$#', `0', ``$0'',
-`_$0(`$1', `$2', `$'`#', `$'`0')')')
-@result{}
-define(`_define_blind', `define(`$1',
-`ifelse(`$3', `0', ``$4'', `$2')')')
-@result{}
-define_blind
-@result{}define_blind
-define_blind(`foo', `arguments were $*')
-@result{}
-foo
-@result{}foo
-foo(`bar')
-@result{}arguments were bar
-define(`blah', defn(`foo'))
-@result{}
-blah
-@result{}blah
-blah(`a', `b')
-@result{}arguments were a,b
-defn(`blah')
-@result{}ifelse(`$#', `0', ``$0'', `arguments were $*')
-@end example
+For an example of a way to make defining blind macros easier, see
+@ref{Composition}.
 
 @cindex multibranches
 @cindex switch statement
 @cindex case statement
-However, @code{ifelse} can take more than four arguments.  If given more
+The macro @code{ifelse} can take more than four arguments.  If given more
 than four arguments, @code{ifelse} works like a @code{case} or @code{switch}
 statement in traditional programming languages.  If @var{string-1} and
 @var{string-2} are equal, @code{ifelse} expands into @var{equal-1}, otherwise
@@ -3725,6 +3676,255 @@ Foreach
 from the best elements of both of these implementations to create robust
 macros (or @pxref{Improved foreach, , Answers}).
 
+@node Stacks
+@section Working with definition stacks
+
+@cindex definition stack
+@cindex pushdef stack
+@cindex stack, macro definition
+Thanks to @code{pushdef}, manipulation of a stack is an intrinsic
+operation in @code{m4}.  Normally, only the topmost definition in a
+stack is important, but sometimes, it is desirable to manipulate the
+entire definition stack.
+
+@deffn Composite stack_foreach (@var{macro}, @var{action})
+@deffnx Composite stack_foreach_lifo (@var{macro}, @var{action})
+For each of the @code{pushdef} definitions associated with @var{macro},
+invoke the macro @var{action} with a single argument of that definition.
+@code{stack_foreach} visits the oldest definition first, while
+@code{stack_foreach_lifo} visits the current definition first.
+@var{action} should not modify or dereference @var{macro}.  There are a
+few special macros, such as @code{defn}, which cannot be used as the
+@var{macro} parameter.
+@end deffn
+
+A sample implementation of these macros is distributed in the file
+@file{m4-@value{VERSION}/@/examples/@/stack.m4}.
+
+@comment examples
+@example
+$ @kbd{m4 -I examples}
+include(`stack.m4')
+@result{}
+pushdef(`a', `1')pushdef(`a', `2')pushdef(`a', `3')
+@result{}
+define(`show', ``$1'
+')
+@result{}
+stack_foreach(`a', `show')dnl
+@result{}1
+@result{}2
+@result{}3
+stack_foreach_lifo(`a', `show')dnl
+@result{}3
+@result{}2
+@result{}1
+@end example
+
+Now for the implementation.  Note the definition of a helper macro,
+@code{_stack_reverse}, which destructively swaps the contents of one
+stack of definitions into the reverse order in the temporary macro
+@samp{tmp-$1}.  By calling the helper twice, the original order is
+restored back into the macro @samp{$1}; since the operation is
+destructive, this explains why @samp{$1} must not be modified or
+dereferenced during the traversal.  The caller can then inject
+additional code to pass the definition currently being visited to
+@samp{$2}.  The choice of helper names is intentional; since @samp{-} is
+not valid as part of a macro name, there is no risk of conflict with a
+valid macro name, and the code is guaranteed to use @code{defn} where
+necessary.  Finally, note that any macro used in the traversal of a
+@code{pushdef} stack, such as @code{pushdef} or @code{defn}, cannot be
+handled by @code{stack_foreach}, since the macro would temporarily be
+undefined during the algorithm.
+
+@comment examples
+@example
+$ @kbd{m4 -I examples}
+undivert(`stack.m4')dnl
+@result{}divert(`-1')
+@result{}# stack_foreach(action, macro)
+@result{}# Invoke ACTION with a single argument of each definition
+@result{}# from the definition stack of MACRO, starting with the oldest.
+@result{}define(`stack_foreach',
+@result{}`_stack_reverse(`$1', `tmp-$1')'dnl
+@result{}`_stack_reverse(`tmp-$1', `$1', `$2(defn(`$1'))')')
+@result{}# stack_foreach_lifo(action, macro)
+@result{}# Invoke ACTION with a single argument of each definition
+@result{}# from the definition stack of MACRO, starting with the newest.
+@result{}define(`stack_foreach_lifo',
+@result{}`_stack_reverse(`$1', `tmp-$1', `$2(defn(`$1'))')'dnl
+@result{}`_stack_reverse(`tmp-$1', `$1')')
+@result{}define(`_stack_reverse',
+@result{}`ifdef(`$1', `pushdef(`$2', defn(`$1'))$3`'popdef(`$1')$0($@@)')')
+@result{}divert`'dnl
+@end example
+
+@node Composition
+@section Building macros with macros
+
+@cindex macro composition
+@cindex composing macros
+Since m4 is a macro language, it is possible to write macros that
+can build other macros.  First on the list is a way to automate the
+creation of blind macros.
+
+@cindex macro, blind
+@cindex blind macro
+@deffn Composite define_blind (@var{name}, @ovar{value})
+Defines @var{name} as a blind macro, such that @var{name} will expand to
+@var{value} only when given explicit arguments.  @var{value} should not
+be the result of @code{defn} (@pxref{Defn}).  This macro is only
+recognized with parameters, and results in an empty string.
+@end deffn
+
+Defining a macro to define another macro can be a bit tricky.  We want
+to use a literal @samp{$#} in the argument to the nested @code{define}.
+However, if @samp{$} and @samp{#} are adjacent in the definition of
+@code{define_blind}, then it would be expanded as the number of
+arguments to @code{define_blind} rather than the intended number of
+arguments to @var{name}.  The solution is to pass the difficult
+characters through extra arguments to a helper macro
+@code{_define_blind}.  When composing macros, it is a common idiom to
+need a helper macro to concatenate text that forms parameters in the
+composed macro, rather than interpreting the text as a parameter of the
+composing macro.
+
+As for the limitation against using @code{defn}, there are two reasons.
+If a macro was previously defined with @code{define_blind}, then it can
+safely be renamed to a new blind macro using plain @code{define}; using
+@code{define_blind} to rename it just adds another layer of
+@code{ifelse}, occupying memory and slowing down execution.  And if a
+macro is a builtin, then it would result in an attempt to define a macro
+consisting of both text and a builtin token; this is not supported, and
+the builtin token is flattened to an empty string.
+
+With that explanation, here's the definition, and some sample usage.
+Notice that @code{define_blind} is itself a blind macro.
+
+@example
+$ @kbd{m4 -d}
+define(`define_blind', `ifelse(`$#', `0', ``$0'',
+`_$0(`$1', `$2', `$'`#', `$'`0')')')
+@result{}
+define(`_define_blind', `define(`$1',
+`ifelse(`$3', `0', ``$4'', `$2')')')
+@result{}
+define_blind
+@result{}define_blind
+define_blind(`foo', `arguments were $*')
+@result{}
+foo
+@result{}foo
+foo(`bar')
+@result{}arguments were bar
+define(`blah', defn(`foo'))
+@result{}
+blah
+@result{}blah
+blah(`a', `b')
+@result{}arguments were a,b
+defn(`blah')
+@result{}ifelse(`$#', `0', ``$0'', `arguments were $*')
+@end example
+
+@cindex currying arguments
+@cindex argument currying
+Another interesting composition tactic is argument @dfn{currying}, or
+factoring a macro that takes multiple arguments for use in a context
+that provides exactly one argument.
+
+@deffn Composite curry (@var{macro}, @dots{})
+Expand to a macro call that takes exactly one argument, then appends
+that argument to the original arguments and invokes @var{macro} with the
+resulting list of arguments.
+@end deffn
+
+A demonstration of currying makes the intent of this macro a little more
+obvious.  The macro @code{stack_foreach} mentioned earlier is an example
+of a context that provides exactly one argument to a macro name.  But
+coupled with currying, we can invoke @code{reverse} with two arguments
+for each definition of a macro stack.  This example uses the file
+@file{m4-@value{VERSION}/@/examples/@/curry.m4} included in the
+distribution.
+
+@comment examples
+@example
+$ @kbd{m4 -I examples}
+include(`curry.m4')include(`stack.m4')
+@result{}
+define(`reverse', `ifelse(`$#', `0', , `$#', `1', ``$1'',
+                          `reverse(shift($@@)), `$1'')')
+@result{}
+pushdef(`a', `1')pushdef(`a', `2')pushdef(`a', `3')
+@result{}
+stack_foreach(`a', `:curry(`reverse', `4')')
+@result{}:1, 4:2, 4:3, 4
+curry(`curry', `reverse', `1')(`2')(`3')
+@result{}3, 2, 1
+@end example
+
+Now for the implementation.  Notice how @code{curry} leaves off with a
+macro name but no open parenthesis, while still in the middle of
+collecting arguments for @samp{$1}.  The macro @code{_curry} is the
+helper macro that takes one argument, then adds it to the list and
+finally supplies the closing parenthesis.  The use of a comma inside the
+@code{shift} call allows currying to also work for a macro that takes
+one argument, although it often makes more sense to invoke that macro
+directly rather than going through @code{curry}.
+
+@comment examples
+@example
+$ @kbd{m4 -I examples}
+undivert(`curry.m4')dnl
+@result{}divert(`-1')
+@result{}# curry(macro, args)
+@result{}# Expand to a macro call that takes one argument, then invoke
+@result{}# macro(args, extra).
+@result{}define(`curry', `$1(shift($@@,)_$0')
+@result{}define(`_curry', ``$1')')
+@result{}divert`'dnl
+@end example
+
+@cindex renaming macros
+@cindex copying macros
+@cindex macros, copying
+Putting the last few concepts together, it is possible to copy or rename
+an entire stack of macro definitions.
+
+@deffn Composite copy (@var{source}, @var{dest})
+@deffnx Composite rename (@var{source}, @var{dest})
+Ensure that @var{dest} is undefined, then define it to the same stack of
+definitions currently in @var{source}.  @code{copy} leaves @var{source}
+unchanged, while @code{rename} undefines @var{source}.  There are only a
+few macros, such as @code{copy} or @code{defn}, which cannot be copied
+via this macro.
+@end deffn
+
+The implementation is relatively straightforward:
+
+@comment examples
+@example
+$ @kbd{m4 -I examples}
+include(`curry.m4')include(`stack.m4')
+@result{}
+define(`rename', `copy($@@)undefine(`$1')')dnl
+define(`copy', `ifdef(`$2', `errprint(`$2 already defined
+')m4exit(`1')',
+   `stack_foreach(`$1', `curry(`pushdef', `$2')')')')dnl
+pushdef(`a', `1')pushdef(`a', defn(`divnum'))pushdef(`a', `2')
+@result{}
+copy(`a', `b')
+@result{}
+rename(`b', `c')
+@result{}
+a b c
+@result{}2 b 2
+popdef(`a', `c')a c
+@result{}0 0
+popdef(`a', `c')a c
+@result{}1 1
+@end example
+
 @node Debugging
 @chapter How to debug macros and input
 
diff --git a/examples/Makefile.am b/examples/Makefile.am
index 47bf0c0..b7ce78c 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -23,6 +23,7 @@ EXTRA_DIST =\
 capitalize.m4 \
 capitalize2.m4 \
 comments.m4 \
+curry.m4 \
 ddivert.m4 \
 debug.m4 \
 esyscmd.m4 \
@@ -55,6 +56,7 @@ pushpop.m4 \
 quote.m4 \
 regexp.m4 \
 reverse.m4 \
+stack.m4 \
 stackovf.sh \
 sync-lines.m4 \
 sysv-args.m4 \
diff --git a/examples/curry.m4 b/examples/curry.m4
new file mode 100644
index 0000000..00997c3
--- /dev/null
+++ b/examples/curry.m4
@@ -0,0 +1,7 @@
+divert(`-1')
+# curry(macro, args)
+# Expand to a macro call that takes one argument, then invoke
+# macro(args, extra).
+define(`curry', `$1(shift($@,)_$0')
+define(`_curry', ``$1')')
+divert`'dnl
diff --git a/examples/stack.m4 b/examples/stack.m4
new file mode 100644
index 0000000..ae3c48e
--- /dev/null
+++ b/examples/stack.m4
@@ -0,0 +1,16 @@
+divert(`-1')
+# stack_foreach(action, macro)
+# Invoke ACTION with a single argument of each definition
+# from the definition stack of MACRO, starting with the oldest.
+define(`stack_foreach',
+`_stack_reverse(`$1', `tmp-$1')'dnl
+`_stack_reverse(`tmp-$1', `$1', `$2(defn(`$1'))')')
+# stack_foreach_lifo(action, macro)
+# Invoke ACTION with a single argument of each definition
+# from the definition stack of MACRO, starting with the newest.
+define(`stack_foreach_lifo',
+`_stack_reverse(`$1', `tmp-$1', `$2(defn(`$1'))')'dnl
+`_stack_reverse(`tmp-$1', `$1')')
+define(`_stack_reverse',
+`ifdef(`$1', `pushdef(`$2', defn(`$1'))$3`'popdef(`$1')$0($@)')')
+divert`'dnl
-- 
1.6.0.4


From 5ace13749e5afae1351d9b6035c2ba9309ac20cd Mon Sep 17 00:00:00 2001
From: Eric Blake <[email protected]>
Date: Tue, 4 Nov 2008 15:33:07 -0700
Subject: [PATCH] Document optimized forloop.

* doc/m4.texinfo (Improved forloop): Mention alternate style that
avoids define overhead.
* examples/forloop3.m4: New file.
* examples/Makefile.am (EXTRA_DIST): Distribute it.

Signed-off-by: Eric Blake <[email protected]>
---
 ChangeLog            |    6 +++++
 doc/m4.texinfo       |   52 ++++++++++++++++++++++++++++++++++++++++++++++++++
 examples/Makefile.am |    1 +
 examples/forloop3.m4 |   13 ++++++++++++
 4 files changed, 72 insertions(+), 0 deletions(-)
 create mode 100644 examples/forloop3.m4

diff --git a/ChangeLog b/ChangeLog
index a0ca8e8..c34b964 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2008-11-26  Eric Blake  <[email protected]>
 
+	Document optimized forloop.
+	* doc/m4.texinfo (Improved forloop): Mention alternate style that
+	avoids define overhead.
+	* examples/forloop3.m4: New file.
+	* examples/Makefile.am (EXTRA_DIST): Distribute it.
+
 	Document copy composite using stack_foreach and curry.
 	* doc/m4.texinfo (Stacks): New node, to document pushdef stack
 	manipulation.
diff --git a/doc/m4.texinfo b/doc/m4.texinfo
index 152dffd..f77a6b5 100644
--- a/doc/m4.texinfo
+++ b/doc/m4.texinfo
@@ -8113,6 +8113,58 @@ Improved forloop
 @error{}m4:stdin:12: recursion limit of 9 exceeded, use -L<N> to change it
 @end example
 
+One more optimization is still possible.  Instead of repeatedly
+assigning a variable then invoking or dereferencing it, it is possible
+to pass the current iterator value as a single argument.  Coupled with
+@code{curry} if other arguments are needed (@pxref{Composition}), or
+with helper macros if the argument is needed in more than one place in
+the expansion, the output can be generated with three, rather than four,
+macros of overhead per iteration.  Notice how the file
+@file{m4-@value{VERSION}/@/examples/@/forloop3.m4} rearranges the
+arguments of the helper @code{_forloop} to take two arguments that are
+placed around the current value.  By splitting a balanced set of
+parantheses across multiple arguments, the helper macro can now be
+shared by @code{forloop} and the new @code{forloop_arg}.
+
+@comment examples
+@example
+$ @kbd{m4 -I examples}
+include(`forloop3.m4')
+@result{}
+undivert(`forloop3.m4')dnl
+@result{}divert(`-1')
+@result{}# forloop_arg(from, to, macro) - invoke MACRO(value) for
+@result{}#   each value between FROM and TO, without define overhead
+@result{}define(`forloop_arg', `ifelse(eval(`($1) <= ($2)'), `1',
+@result{}  `_forloop(`$1', eval(`$2'), `$3(', `)')')')
+@result{}# forloop(var, from, to, stmt) - refactored to share code
+@result{}define(`forloop', `ifelse(eval(`($2) <= ($3)'), `1',
+@result{}  `pushdef(`$1')_forloop(eval(`$2'), eval(`$3'),
+@result{}    `define(`$1',', `)$4')popdef(`$1')')')
+@result{}define(`_forloop',
+@result{}  `$3`$1'$4`'ifelse(`$1', `$2', `',
+@result{}    `$0(incr(`$1'), `$2', `$3', `$4')')')
+@result{}divert`'dnl
+forloop(`i', `1', `3', ` i')
+@result{} 1 2 3
+define(`echo', `$@@')
+@result{}
+forloop_arg(`1', `3', ` echo')
+@result{} 1 2 3
+include(`curry.m4')
+@result{}
+forloop_arg(`1', `3', `curry(`pushdef', `a')')
+@result{}
+a
+@result{}3
+popdef(`a')a
+@result{}2
+popdef(`a')a
+@result{}1
+popdef(`a')a
+@result{}a
+@end example
+
 Of course, it is possible to make even more improvements, such as
 adding an optional step argument, or allowing iteration through
 descending sequences.  @acronym{GNU} Autoconf provides some of these
diff --git a/examples/Makefile.am b/examples/Makefile.am
index b7ce78c..1a97347 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -38,6 +38,7 @@ foreachq3.m4 \
 foreachq4.m4 \
 forloop.m4 \
 forloop2.m4 \
+forloop3.m4 \
 fstab.m4 \
 hanoi.m4 \
 incl-test.m4 \
diff --git a/examples/forloop3.m4 b/examples/forloop3.m4
new file mode 100644
index 0000000..98db20f
--- /dev/null
+++ b/examples/forloop3.m4
@@ -0,0 +1,13 @@
+divert(`-1')
+# forloop_arg(from, to, macro) - invoke MACRO(value) for
+#   each value between FROM and TO, without define overhead
+define(`forloop_arg', `ifelse(eval(`($1) <= ($2)'), `1',
+  `_forloop(`$1', eval(`$2'), `$3(', `)')')')
+# forloop(var, from, to, stmt) - refactored to share code
+define(`forloop', `ifelse(eval(`($2) <= ($3)'), `1',
+  `pushdef(`$1')_forloop(eval(`$2'), eval(`$3'),
+    `define(`$1',', `)$4')popdef(`$1')')')
+define(`_forloop',
+  `$3`$1'$4`'ifelse(`$1', `$2', `',
+    `$0(incr(`$1'), `$2', `$3', `$4')')')
+divert`'dnl
-- 
1.6.0.4
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.