From 4e9078ba0b9a919ee35a1ff8b6bd8c764362f33e Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Thu, 4 Oct 2007 12:29:45 -0600 Subject: [PATCH] Provide better short-circuiting operation. * lib/m4sugar/m4sugar.m4 (m4_cond, m4_newline): New macros. (m4_text_wrap): Use it. Also avoid useless m4_for. * lib/m4sugar/m4sh.m4 (_AS_QUOTE_IFELSE, AS_LITERAL_IF): Use new macro. (_AS_IDENTIFIER_IF): Likewise, and fix bug when $1 is [,]. * lib/autotest/general.m4 (_AT_DECIDE_TRACEABLE): Use new macros to avoid regexps. * doc/autoconf.texi (Redefined M4 Macros): Expand m4_if documentation. Sort m4_mkstemp, m4_undefine. Move m4_ifndef... (Conditional constructs): ...here, to new section. Also document m4_cond, m4_ifval, m4_n, m4_ifvaln, m4_ifset, m4_case, m4_bmatch, m4_bpatsubsts, and m4_default. (Looping constructs): Document m4_shiftn, m4_shift2, m4_shift3, m4_do. Signed-off-by: Eric Blake --- ChangeLog | 18 +++++ doc/autoconf.texi | 209 ++++++++++++++++++++++++++++++++++++++++-------- lib/autotest/general.m4 | 22 ++--- lib/m4sugar/m4sh.m4 | 35 ++++---- lib/m4sugar/m4sugar.m4 | 58 ++++++++++++-- 5 files changed, 274 insertions(+), 68 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9052228d..57dfbb5a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,21 @@ +2007-10-05 Eric Blake + + Provide better short-circuiting operation. + * lib/m4sugar/m4sugar.m4 (m4_cond, m4_newline): New macros. + (m4_text_wrap): Use it. Also avoid useless m4_for. + * lib/m4sugar/m4sh.m4 (_AS_QUOTE_IFELSE, AS_LITERAL_IF): Use + new macro. + (_AS_IDENTIFIER_IF): Likewise, and fix bug when $1 is [,]. + * lib/autotest/general.m4 (_AT_DECIDE_TRACEABLE): Use new macros + to avoid regexps. + * doc/autoconf.texi (Redefined M4 Macros): Expand m4_if + documentation. Sort m4_mkstemp, m4_undefine. Move m4_ifndef... + (Conditional constructs): ...here, to new section. Also document + m4_cond, m4_ifval, m4_n, m4_ifvaln, m4_ifset, m4_case, m4_bmatch, + m4_bpatsubsts, and m4_default. + (Looping constructs): Document m4_shiftn, m4_shift2, m4_shift3, + m4_do. + 2007-10-04 Eric Blake Fix recent testsuite failures. diff --git a/doc/autoconf.texi b/doc/autoconf.texi index bd67ab19..ecc496b4 100644 --- a/doc/autoconf.texi +++ b/doc/autoconf.texi @@ -449,6 +449,7 @@ Using @command{autom4te} Programming in M4sugar * Redefined M4 Macros:: M4 builtins changed in M4sugar +* Conditional constructs:: Conditions in M4 * Looping constructs:: Iteration in M4 * Evaluation Macros:: More quotation and evaluation control * Text processing Macros:: String manipulation in M4 @@ -10070,6 +10071,7 @@ M4sugar''. @menu * Redefined M4 Macros:: M4 builtins changed in M4sugar +* Conditional constructs:: Conditions in M4 * Looping constructs:: Iteration in M4 * Evaluation Macros:: More quotation and evaluation control * Text processing Macros:: String manipulation in M4 @@ -10136,15 +10138,10 @@ This macro corresponds to @code{m4exit}. @defmacx m4_if (@var{string-1}, @var{string-2}, @var{equal}, @ovar{not-equal}) @defmacx m4_if (@var{string-1}, @var{string-2}, @var{equal}, @dots{}) @msindex{if} -This macro corresponds to @code{ifelse}. -@end defmac - -@defmac m4_ifndef (@var{macro}, @var{if-not-defined}, @ovar{if-defined}) -@msindex{ifndef} -This is shorthand for: -@example -m4_ifdef([@var{macro}], [@var{if-defined}], [@var{if-not-defined}]) -@end example +This macro corresponds to @code{ifelse}. @var{string-1} and +@var{string-2} are compared literally, so usually one of the two +arguments is passed unquoted. @xref{Conditional constructs} for more +conditional idioms. @end defmac @defmac m4_include (@var{file}) @@ -10154,11 +10151,26 @@ m4_ifdef([@var{macro}], [@var{if-defined}], [@var{if-not-defined}]) Like the M4 builtins, but warn against multiple inclusions of @var{file}. @end defmac +@defmac m4_mkstemp (@var{template}) +@defmacx m4_maketemp (@var{template}) +@msindex{maketemp} +@msindex{mkstemp} +Posix requires @code{maketemp} to replace the trailing @samp{X} +characters in @var{template} with the process id, without regards to the +existence of a file by that name, but this a security hole. When this +was pointed out to the Posix folks, they agreed to invent a new macro +@code{mkstemp} that always creates a uniquely named file, but not all +versions of @acronym{GNU} M4 support the new macro. In M4sugar, +@code{m4_maketemp} and @code{m4_mkstemp} are synonyms for each other, +and both have the secure semantics regardless of which macro the +underlying M4 provides. +@end defmac + @defmac m4_bpatsubst (@var{string}, @var{regexp}, @ovar{replacement}) @msindex{bpatsubst} This macro corresponds to @code{patsubst}. The name @code{m4_patsubst} -is kept for future versions of M4sh, on top of @acronym{GNU} M4 which will -provide extended regular expression syntax via @code{epatsubst}. +is kept for future versions of M4sugar, once @acronym{GNU} M4 2.0 is +released and supports extended regular expression syntax. @end defmac @defmac m4_popdef (@var{macro}) @@ -10170,8 +10182,21 @@ defined. See @code{m4_undefine}. @defmac m4_bregexp (@var{string}, @var{regexp}, @ovar{replacement}) @msindex{bregexp} This macro corresponds to @code{regexp}. The name @code{m4_regexp} -is kept for future versions of M4sh, on top of @acronym{GNU} M4 which will -provide extended regular expression syntax via @code{eregexp}. +is kept for future versions of M4sugar, once @acronym{GNU} M4 2.0 is +released and supports extended regular expression syntax. +@end defmac + +@defmac m4_undefine (@var{macro}) +@msindex{undefine} +Unlike the M4 builtin, this macro fails if @var{macro} is not +defined. Use + +@example +m4_ifdef([@var{macro}], [m4_undefine([@var{macro}])]) +@end example + +@noindent +to recover the behavior of the builtin. @end defmac @defmac m4_wrap (@var{text}) @@ -10200,32 +10225,131 @@ m4_wrap([foo]) @end example @end defmac -@defmac m4_undefine (@var{macro}) -@msindex{undefine} -Unlike the M4 builtin, this macro fails if @var{macro} is not -defined. Use +@node Conditional constructs +@subsection Conditional constructs + +The following macros provide additional conditional contructs, as +convenience wrappers around @code{m4_if}. + +@defmac m4_bmatch (@var{string}, @var{regex-1}, @var{value-1}, @dots{}, @ + @ovar{default}) +@msindex{bmatch} +The string @var{string} is repeatedly compared against a series of +@var{regex} arguments; if a match is found, the expansion is the +corresponding @var{value}, otherwise, the macro moves on to the next +@var{regex}. If no @var{regex} match, then the result is the optional +@var{default}, or nothing. +@end defmac + +@defmac m4_bpatsubsts (@var{string}, @var{regex-1}, @var{subst-1}, @dots{}) +@msindex{bpatsubsts} +The string @var{string} is altered by @var{regex-1} and @var{subst-1}, +as if by: @example -m4_ifdef([@var{macro}], [m4_undefine([@var{macro}])]) +m4_bpatsubst([[@var{string}]], [@var{regex}], [@var{subst}]) @end example @noindent -to recover the behavior of the builtin. +The result of the substitution is then passed through the next set of +@var{regex} and @var{subst}, and so forth. An empty @var{subst} implies +deletion of any matched portions in the current string. Note that this +macro over-quotes @var{string}; this behavior is intentional, so that +the result of each step of the recursion remains as a quoted string. +However, it means that anchors (@samp{^} and @samp{$} in the @var{regex} +will line up with the extra quotations, and not the characters of the +original string. +@end defmac + +@defmac m4_case (@var{string}, @var{value-1}, @var{if-value-1}, @dots{}, @ + @ovar{default}) +@msindex{case} +Test @var{string} against multiple @var{value} possibilities, resulting +in the first @var{if-value} for a match, or in the optional +@var{default}. This is shorthand for: +@example +m4_if([@var{string}], [@var{value-1}], [@var{if-value-1}], + [@var{string}], [@var{value-2}], [@var{if-value-2}], @dots{}, + [@var{default}]) +@end example +@end defmac + +@defmac m4_cond (@var{test-1}, @var{value-1}, @var{if-value-1}, @ + @var{test-2}, @var{value-2}, @var{if-value-2}, @dots{}, @ovar{default}) +@msindex{cond} +Similar to @code{m4_if}, except that each @var{test} is expanded only +when it is encountered. This is useful for short-circuiting expensive +tests; while @code{m4_if} requires all its strings to be expanded up +front before doing comparisons, @code{m4_cond} only expands a @var{test} +when all earlier tests have failed. + +For an example, these two sequences give the same result, but in the +case where @samp{$1} does not contain a backslash, the @code{m4_cond} +version only expands @code{m4_index} once, instead of five times, for +faster computation if this is a common case for @samp{$1}. Notice that +every third argument is unquoted for @code{m4_if}, and quoted for +@code{m4_cond}: + +@example +m4_if(m4_index([$1], [\]), [-1], [$2], + m4_eval(m4_index([$1], [\\]) >= 0), [1], [$2], + m4_eval(m4_index([$1], [\$]) >= 0), [1], [$2], + m4_eval(m4_index([$1], [\`]) >= 0), [1], [$3], + m4_eval(m4_index([$1], [\"]) >= 0), [1], [$3], + [$2]) +m4_cond([m4_index([$1], [\])], [-1], [$2], + [m4_eval(m4_index([$1], [\\]) >= 0)], [1], [$2], + [m4_eval(m4_index([$1], [\$]) >= 0)], [1], [$2], + [m4_eval(m4_index([$1], [\`]) >= 0)], [1], [$3], + [m4_eval(m4_index([$1], [\"]) >= 0)], [1], [$3], + [$2]) +@end example +@end defmac + +@defmac m4_default (@var{expr-1}, @var{expr-2}) +@msindex{default} +If @var{expr-1} is not empty, use it. Otherwise, expand to +@var{expr-2}. Useful for providing a fixed default if the expression +that results in @var{expr-1} would otherwise be empty. @end defmac -@defmac m4_maketemp (@var{template}) -@defmacx m4_mkstemp (@var{template}) -@msindex{maketemp} -@msindex{mkstemp} -Posix requires @code{maketemp} to replace the trailing @samp{X} -characters in @var{template} with the process id, without regards to the -existence of a file by that name, but this a security hole. When this -was pointed out to the Posix folks, they agreed to invent a new macro -@code{mkstemp} that always creates a uniquely named file, but not all -versions of @acronym{GNU} M4 support the new macro. In M4sugar, -@code{m4_maketemp} and @code{m4_mkstemp} are synonyms for each other, -and both have the secure semantics regardless of which macro the -underlying M4 provides. +@defmac m4_ifndef (@var{macro}, @var{if-not-defined}, @ovar{if-defined}) +@msindex{ifndef} +This is shorthand for: +@example +m4_ifdef([@var{macro}], [@var{if-defined}], [@var{if-not-defined}]) +@end example +@end defmac + +@defmac m4_ifset (@var{macro}, @ovar{if-true}, @ovar{if-false}) +@msindex{ifset} +If @var{macro} is undefined, or is defined as the empty string, expand +to @var{if-false}. Otherwise, expands to @var{if-true}. Similar to: +@example +m4_ifval(m4_defn([@var{macro}]), [@var{if-true}], [@var{if-false}]) +@end example +@noindent +except that it is not an error if @var{macro} is undefined. +@end defmac + +@defmac m4_ifval (@var{cond}, @ovar{if-true}, @ovar{if-false}) +@msindex{ifval} +Expands to @var{if-true} if @var{cond} is not empty, otherwise to +@var{if-false}. This is shorthand for: +@example +m4_if([@var{cond}], [], [@var{if-true}], [@var{if-false}]) +@end example +@end defmac + +@defmac m4_ifvaln (@var{cond}, @ovar{if-true}, @ovar{if-false}) +@msindex{ifvaln} +Similar to @code{m4_ifval}, except guarantee that a newline is present +after any non-empty expansion. +@end defmac + +@defmac m4_n (@var{text}) +@msindex{n} +Expand to @var{text}, and add a newline if @var{text} is not empty. @end defmac @@ -10269,6 +10393,27 @@ The deprecated macro @code{AC_FOREACH} is an alias of @code{m4_foreach_w}. @end defmac +The following macros are useful in implementing recursive algorithms. + +@defmac m4_do(@dots{}) +@msindex{do} +This macro loops over its arguments and expands each one in sequence. +Its main use is for readability; it allows the use of indentation and +fewer @code{dnl} to result in the same expansion. +@end defmac + +@defmac m4_shiftn (@var{count}, @dots{}) +@defmacx m4_shift2 (@dots{}) +@defmacx m4_shift3 (@dots{}) +@msindex{shift2} +@msindex{shift3} +@msindex{shiftn} +@code{m4_shiftn} performs @var{count} iterations of @code{m4_shift}, +along with validation that enough arguments were passed in to match the +shift count. @code{m4_shift2} and @code{m4_shift3} are specializations +of @code{m4_shiftn} that are more efficient for two and three shifts, +respectively. +@end defmac @node Evaluation Macros diff --git a/lib/autotest/general.m4 b/lib/autotest/general.m4 index 5390d498..09864246 100644 --- a/lib/autotest/general.m4 +++ b/lib/autotest/general.m4 @@ -1426,18 +1426,19 @@ m4_define([AT_CHECK_NOESCAPE], # to safely expand arbitrary COMMANDS in an argument list, so the below tests # examine COMMANDS unexpanded. m4_define([_AT_DECIDE_TRACEABLE], -[dnl Utility macros. -m4_pushdef([at_lf], [ -])[]dnl +dnl Utility macro. dnl dnl Examine COMMANDS for a reason to never trace COMMANDS. -m4_pushdef([at_reason], - m4_bmatch([$1], - [`.*`], [[a `...` command substitution]], - [\$(], [[a $(...) command substitution]], - [\${], [[a ${...} parameter expansion]], - at_lf, [[an embedded newline]], - [[]]dnl No reason. +[m4_pushdef([at_reason], +m4_cond([m4_eval(m4_index([$1], [`]) >= 0)], [1], + [[a `...` command substitution]], + [m4_eval(m4_index([$1], [$(]) >= 0)], [1], + [[a $(...) command substitution]], + [m4_eval(m4_index([$1], [${]) >= 0)], [1], + [[a ${...} parameter expansion]], + [m4_eval(m4_index([$1], m4_newline) >= 0)], [1], + [[an embedded newline]], + []dnl No reason. ))dnl dnl m4_ifval(m4_defn([at_reason]), @@ -1447,7 +1448,6 @@ dnl We know at build time that tracing COMMANDS is always safe. [test -n "$at_traceon"], dnl COMMANDS may contain parameter expansions; expand them at runtime. [test -n "$at_traceon" && at_check_newline "AS_ESCAPE([$1], [`\"])"])])[]dnl -m4_popdef([at_lf])[]dnl m4_popdef([at_reason])]) diff --git a/lib/m4sugar/m4sh.m4 b/lib/m4sugar/m4sh.m4 index 26326f5e..8d441ab0 100644 --- a/lib/m4sugar/m4sh.m4 +++ b/lib/m4sugar/m4sh.m4 @@ -588,12 +588,12 @@ m4_define([_AS_ESCAPE], # The current implementation caters to the common case of no backslashes, # to minimize m4_index expansions (hence the nested if). m4_define([_AS_QUOTE_IFELSE], -[m4_if(m4_index([$1], [\]), [-1], [$2], - [m4_if(m4_eval(m4_index([$1], [\\]) >= 0), [1], [$2], - m4_eval(m4_index([$1], [\$]) >= 0), [1], [$2], - m4_eval(m4_index([$1], [\`]) >= 0), [1], [$3], - m4_eval(m4_index([$1], [\"]) >= 0), [1], [$3], - [$2])])]) +[m4_cond([m4_index([$1], [\])], [-1], [$2], + [m4_eval(m4_index([$1], [\\]) >= 0)], [1], [$2], + [m4_eval(m4_index([$1], [\$]) >= 0)], [1], [$2], + [m4_eval(m4_index([$1], [\`]) >= 0)], [1], [$3], + [m4_eval(m4_index([$1], [\"]) >= 0)], [1], [$3], + [$2])]) # _AS_QUOTE(STRING, [CHARS = `"]) @@ -1248,11 +1248,12 @@ m4_define([AS_IDENTIFIER_IF], [_$0($@)], [_$0(m4_bpatsubst([[$1]], [@&t@]), [$2], [$3])])]) m4_define([_AS_IDENTIFIER_IF], -[m4_if([$1], [], [$3], - m4_translit([[$1]], ]m4_dquote(m4_defn([m4_cr_symbols2]))[), [], - [m4_if(m4_len(m4_translit(m4_format([[%.1s]], [$1]), ]]dnl -m4_dquote(m4_dquote(m4_defn([m4_cr_symbols1])))[[)), [0], [$2], [$3])], - [$3])]) +[m4_cond([[$1]], [], [$3], + [m4_eval(m4_len(m4_translit([[$1]], ]]dnl +m4_dquote(m4_dquote(m4_defn([m4_cr_symbols2])))[[)) > 0)], [1], [$3], + [m4_len(m4_translit(m4_format([[%.1s]], [$1]), ]]dnl +m4_dquote(m4_dquote(m4_defn([m4_cr_symbols1])))[[))], [0], [$2], [$3])]) + # AS_LITERAL_IF(EXPRESSION, IF-LITERAL, IF-NOT-LITERAL) # ----------------------------------------------------- @@ -1280,12 +1281,12 @@ m4_dquote(m4_dquote(m4_defn([m4_cr_symbols1])))[[)), [0], [$2], [$3])], # Rather than expand m4_defn every time AS_LITERAL_IF is expanded, we # inline its expansion up front. m4_define([AS_LITERAL_IF], -[m4_if(m4_eval(m4_index(m4_quote($1), [@S|@]) == -1), [0], [$3], - m4_index(m4_translit(m4_quote($1), - [[]`,#]]m4_dquote(m4_defn([m4_cr_symbols2]))[, - [$$$]), - [$]), [-1], [$2], - [$3])]) +[m4_cond([m4_eval(m4_index(m4_quote($1), [@S|@]) == -1)], [0], [$3], + [m4_index(m4_translit(m4_quote($1), + [[]`,#]]m4_dquote(m4_defn([m4_cr_symbols2]))[, + [$$$]), + [$])], [-1], [$2], + [$3])]) # AS_TMPDIR(PREFIX, [DIRECTORY = $TMPDIR [= /tmp]]) diff --git a/lib/m4sugar/m4sugar.m4 b/lib/m4sugar/m4sugar.m4 index d9cf142a..1da5220f 100644 --- a/lib/m4sugar/m4sugar.m4 +++ b/lib/m4sugar/m4sugar.m4 @@ -399,6 +399,42 @@ m4_define([m4_cdr], [m4_dquote(m4_shift($@))])]) +# m4_cond(TEST1, VAL1, IF-VAL1, TEST2, VAL2, IF-VAL2, ..., [DEFAULT]) +# ------------------------------------------------------------------- +# Similar to m4_if, except that each TEST is expanded when encountered. +# If the expansion of TESTn matches the string VALn, the result is IF-VALn. +# The result is DEFAULT if no tests passed. This macro allows +# short-circuiting of expensive tests, where it pays to arrange quick +# filter tests to run first. +# +# For an example, consider a previous implementation of _AS_QUOTE_IFELSE: +# +# m4_if(m4_index([$1], [\]), [-1], [$2], +# m4_eval(m4_index([$1], [\\]) >= 0), [1], [$2], +# m4_eval(m4_index([$1], [\$]) >= 0), [1], [$2], +# m4_eval(m4_index([$1], [\`]) >= 0), [1], [$3], +# m4_eval(m4_index([$1], [\"]) >= 0), [1], [$3], +# [$2]) +# +# Here, m4_index is computed 5 times, and m4_eval 4, even if $1 contains +# no backslash. It is more efficient to do: +# +# m4_cond([m4_index([$1], [\])], [-1], [$2], +# [m4_eval(m4_index([$1], [\\]) >= 0)], [1], [$2], +# [m4_eval(m4_index([$1], [\$]) >= 0)], [1], [$2], +# [m4_eval(m4_index([$1], [\`]) >= 0)], [1], [$3], +# [m4_eval(m4_index([$1], [\"]) >= 0)], [1], [$3], +# [$2]) +# +# In the common case of $1 with no backslash, only one m4_index expansion +# occurs, and m4_eval is avoided altogether. +m4_define([m4_cond], +[m4_if([$#], [0], [m4_fatal([$0: cannot be called without arguments])], + [$#], [1], [$1], + [$#], [2], [m4_fatal([$0: missing an argument])], + [m4_if($1, [$2], [$3], [$0(m4_shift3($@))])])]) + + # m4_map(MACRO, LIST) # ------------------- # Invoke MACRO($1), MACRO($2) etc. where $1, $2... are the elements @@ -1448,6 +1484,13 @@ _m4_define_cr_not([symbols1]) _m4_define_cr_not([symbols2]) +# m4_newline +# ---------- +# Expands to a newline. Exists for formatting reasons. +m4_define([m4_newline], [ +]) + + # m4_re_escape(STRING) # -------------------- # Escape RE active characters in STRING. @@ -1696,15 +1739,14 @@ m4_pushdef([m4_Width], m4_default([$4], 79))dnl m4_pushdef([m4_Cursor], m4_qlen(m4_defn([m4_Prefix1])))dnl m4_pushdef([m4_Separator], [])dnl m4_defn([m4_Prefix1])[]dnl -m4_if(m4_eval(m4_qlen(m4_defn([m4_Prefix1])) > m4_len(m4_Prefix)), - 1, [m4_define([m4_Cursor], m4_len(m4_Prefix)) +m4_cond([m4_eval(m4_qlen(m4_defn([m4_Prefix1])) > m4_len(m4_Prefix))], + [1], [m4_define([m4_Cursor], m4_len(m4_Prefix)) m4_Prefix], - m4_if(m4_eval(m4_qlen(m4_defn([m4_Prefix1])) < m4_len(m4_Prefix)), - [0], [], - [m4_define([m4_Cursor], m4_len(m4_Prefix))[]dnl -m4_for(m4_Space, m4_qlen(m4_defn([m4_Prefix1])), m4_eval(m4_len(m4_Prefix) - 1), - [], [ ])])[]dnl -)[]dnl + [m4_eval(m4_qlen(m4_defn([m4_Prefix1])) < m4_len(m4_Prefix))], + [0], [], + [m4_define([m4_Cursor], m4_len(m4_Prefix))[]dnl +m4_format(%m4_eval(m4_len(m4_Prefix) - 1 - m4_qlen(m4_defn([m4_Prefix1])))[s], + [])])[]dnl m4_foreach_w([m4_Word], [$1], [m4_define([m4_Cursor], m4_eval(m4_Cursor + m4_qlen(m4_defn([m4_Word])) + 1))dnl dnl New line if too long, else insert a space unless it is the first -- 2.11.4.GIT