m4-patches
[Top][All Lists]
Advanced

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

documentation ideas from autoconf


From: Eric Blake
Subject: documentation ideas from autoconf
Date: Wed, 26 Nov 2008 17:58:29 +0000 (UTC)
User-agent: Loom/3.14 (http://gmane.org/)

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 <address@hidden>
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 <address@hidden>
---
 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  <address@hidden>
+
+       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  <address@hidden>
 
        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
address@hidden 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:
-
address@hidden Composite define_blind (@var{name}, @ovar{value})
-Defines @var{name} as a blind macro, such that @var{name} will expand to
address@hidden 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.
address@hidden 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
address@hidden, 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
address@hidden
-
-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
address@hidden to rename it just adds another layer of
address@hidden, 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.
-
address@hidden
-$ @kbd{m4 -d}
-define(`define_blind', `ifelse(`$#', `0', ``$0'',
-`_$0(`$1', `$2', `$'`#', `$'`0')')')
address@hidden
-define(`_define_blind', `define(`$1',
-`ifelse(`$3', `0', ``$4'', `$2')')')
address@hidden
-define_blind
address@hidden
-define_blind(`foo', `arguments were $*')
address@hidden
-foo
address@hidden
-foo(`bar')
address@hidden were bar
-define(`blah', defn(`foo'))
address@hidden
-blah
address@hidden
-blah(`a', `b')
address@hidden were a,b
-defn(`blah')
address@hidden(`$#', `0', ``$0'', `arguments were $*')
address@hidden example
+For an example of a way to make defining blind macros easier, see
address@hidden
 
 @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}).
 
address@hidden Stacks
address@hidden Working with definition stacks
+
address@hidden definition stack
address@hidden pushdef stack
address@hidden 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.
+
address@hidden Composite stack_foreach (@var{macro}, @var{action})
address@hidden 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.
address@hidden visits the oldest definition first, while
address@hidden visits the current definition first.
address@hidden should not modify or dereference @var{macro}.  There are a
+few special macros, such as @code{defn}, which cannot be used as the
address@hidden parameter.
address@hidden deffn
+
+A sample implementation of these macros is distributed in the file
address@hidden@value{VERSION}/@/examples/@/stack.m4}.
+
address@hidden examples
address@hidden
+$ @kbd{m4 -I examples}
+include(`stack.m4')
address@hidden
+pushdef(`a', `1')pushdef(`a', `2')pushdef(`a', `3')
address@hidden
+define(`show', ``$1'
+')
address@hidden
+stack_foreach(`a', `show')dnl
address@hidden
address@hidden
address@hidden
+stack_foreach_lifo(`a', `show')dnl
address@hidden
address@hidden
address@hidden
address@hidden example
+
+Now for the implementation.  Note the definition of a helper macro,
address@hidden, which destructively swaps the contents of one
+stack of definitions into the reverse order in the temporary macro
address@hidden  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
address@hidden  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
address@hidden 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.
+
address@hidden examples
address@hidden
+$ @kbd{m4 -I examples}
+undivert(`stack.m4')dnl
address@hidden(`-1')
address@hidden stack_foreach(action, macro)
address@hidden Invoke ACTION with a single argument of each definition
address@hidden from the definition stack of MACRO, starting with the oldest.
address@hidden(`stack_foreach',
address@hidden(`$1', `tmp-$1')'dnl
address@hidden(`tmp-$1', `$1', `$2(defn(`$1'))')')
address@hidden stack_foreach_lifo(action, macro)
address@hidden Invoke ACTION with a single argument of each definition
address@hidden from the definition stack of MACRO, starting with the newest.
address@hidden(`stack_foreach_lifo',
address@hidden(`$1', `tmp-$1', `$2(defn(`$1'))')'dnl
address@hidden(`tmp-$1', `$1')')
address@hidden(`_stack_reverse',
address@hidden(`$1', `pushdef(`$2', defn(`$1'))$3`'popdef(`$1')$0($@@)')')
address@hidden'dnl
address@hidden example
+
address@hidden Composition
address@hidden Building macros with macros
+
address@hidden macro composition
address@hidden 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.
+
address@hidden macro, blind
address@hidden blind macro
address@hidden Composite define_blind (@var{name}, @ovar{value})
+Defines @var{name} as a blind macro, such that @var{name} will expand to
address@hidden 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.
address@hidden 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
address@hidden, 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
address@hidden  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
address@hidden to rename it just adds another layer of
address@hidden, 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.
+
address@hidden
+$ @kbd{m4 -d}
+define(`define_blind', `ifelse(`$#', `0', ``$0'',
+`_$0(`$1', `$2', `$'`#', `$'`0')')')
address@hidden
+define(`_define_blind', `define(`$1',
+`ifelse(`$3', `0', ``$4'', `$2')')')
address@hidden
+define_blind
address@hidden
+define_blind(`foo', `arguments were $*')
address@hidden
+foo
address@hidden
+foo(`bar')
address@hidden were bar
+define(`blah', defn(`foo'))
address@hidden
+blah
address@hidden
+blah(`a', `b')
address@hidden were a,b
+defn(`blah')
address@hidden(`$#', `0', ``$0'', `arguments were $*')
address@hidden example
+
address@hidden currying arguments
address@hidden 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.
+
address@hidden 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.
address@hidden 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
address@hidden@value{VERSION}/@/examples/@/curry.m4} included in the
+distribution.
+
address@hidden examples
address@hidden
+$ @kbd{m4 -I examples}
+include(`curry.m4')include(`stack.m4')
address@hidden
+define(`reverse', `ifelse(`$#', `0', , `$#', `1', ``$1'',
+                          `reverse(shift($@@)), `$1'')')
address@hidden
+pushdef(`a', `1')pushdef(`a', `2')pushdef(`a', `3')
address@hidden
+stack_foreach(`a', `:curry(`reverse', `4')')
address@hidden:1, 4:2, 4:3, 4
+curry(`curry', `reverse', `1')(`2')(`3')
address@hidden, 2, 1
address@hidden 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
address@hidden 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}.
+
address@hidden examples
address@hidden
+$ @kbd{m4 -I examples}
+undivert(`curry.m4')dnl
address@hidden(`-1')
address@hidden curry(macro, args)
address@hidden Expand to a macro call that takes one argument, then invoke
address@hidden macro(args, extra).
address@hidden(`curry', `$1(shift($@@,)_$0')
address@hidden(`_curry', ``$1')')
address@hidden'dnl
address@hidden example
+
address@hidden renaming macros
address@hidden copying macros
address@hidden macros, copying
+Putting the last few concepts together, it is possible to copy or rename
+an entire stack of macro definitions.
+
address@hidden Composite copy (@var{source}, @var{dest})
address@hidden 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.
address@hidden deffn
+
+The implementation is relatively straightforward:
+
address@hidden examples
address@hidden
+$ @kbd{m4 -I examples}
+include(`curry.m4')include(`stack.m4')
address@hidden
+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')
address@hidden
+copy(`a', `b')
address@hidden
+rename(`b', `c')
address@hidden
+a b c
address@hidden b 2
+popdef(`a', `c')a c
address@hidden 0
+popdef(`a', `c')a c
address@hidden 1
address@hidden 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 <address@hidden>
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 <address@hidden>
---
 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  <address@hidden>
 
+       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
address@hidden 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
address@hidden@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}.
+
address@hidden examples
address@hidden
+$ @kbd{m4 -I examples}
+include(`forloop3.m4')
address@hidden
+undivert(`forloop3.m4')dnl
address@hidden(`-1')
address@hidden forloop_arg(from, to, macro) - invoke MACRO(value) for
address@hidden   each value between FROM and TO, without define overhead
address@hidden(`forloop_arg', `ifelse(eval(`($1) <= ($2)'), `1',
address@hidden  `_forloop(`$1', eval(`$2'), `$3(', `)')')')
address@hidden forloop(var, from, to, stmt) - refactored to share code
address@hidden(`forloop', `ifelse(eval(`($2) <= ($3)'), `1',
address@hidden  `pushdef(`$1')_forloop(eval(`$2'), eval(`$3'),
address@hidden    `define(`$1',', `)$4')popdef(`$1')')')
address@hidden(`_forloop',
address@hidden  `$3`$1'$4`'ifelse(`$1', `$2', `',
address@hidden    `$0(incr(`$1'), `$2', `$3', `$4')')')
address@hidden'dnl
+forloop(`i', `1', `3', ` i')
address@hidden 1 2 3
+define(`echo', `$@@')
address@hidden
+forloop_arg(`1', `3', ` echo')
address@hidden 1 2 3
+include(`curry.m4')
address@hidden
+forloop_arg(`1', `3', `curry(`pushdef', `a')')
address@hidden
+a
address@hidden
+popdef(`a')a
address@hidden
+popdef(`a')a
address@hidden
+popdef(`a')a
address@hidden
address@hidden 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







reply via email to

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