This rather lengthy patch has almost no user visible effects; it's all about testing the m4sugar layer more thoroughly. I would appreciate a second pair of eyes on it, but I think it's pretty low risk despite the amount of code being moved around, and I will probably merge it in about a week if no one comments.
All of the m4sugar macros that iterate over a macro argument list have two implementations. One is in m4sugar.m4 and operates by recursion, processing some of the leading elements of $@ and then re-invoking itself on the remainder, using `m4_shift`. This is the most straightforward way to iterate over an argument list in M4, but it is quadratically expensive in GNU M4 1.4.x. Thus, we have a second implementation of each such macro in m4sugar/foreach.m4. Those work by generating a temporary helper macro that references each argument by number, no matter how many there are, so that $@ only needs to be expanded once. This is linear in the size of the argument list, but less portable (as it uses $10 and up) and much harder to understand. According to the comments in foreach.m4, GNU M4 1.6.x is going to be cleverer about recursive macro expansion, and the straightforward implementations will be *faster* than the non-recursive ones. So we’ve kept the straightforward implementations around, and m4_init replaces them with the foreach.m4 implementations if it detects M4 1.4.x is in use. I say “according to” and “is going to be” because those comments were written in 2008 and, as of 2024, the latest released version of GNU M4 is still from the 1.4.x series. Development branches for versions 1.6.x and 2.x exist (see <https://git.savannah.gnu.org/cgit/m4.git>) but have not seen any activity in the past fourteen and eight years, respectively. This is a *problem* for us because: - If you forget to call m4_init, you get the recursive implementations, which might mean whatever weird one-off test you’re doing hangs. - Some of the recursive implementations have fallen out of sync with the foreach implementations, as seen in the previous two patches. (I noticed this because I forgot to call m4_init in some weird one-off tests.) This patch therefore makes the following changes: - All the recursive implementations are moved from m4sugar.m4 to a new file m4sugar/recursive.m4. - m4sugar.m4 loads m4sugar/foreach.m4 at freeze time, so if you forget to call m4_init, you get the foreach implementations. This should also make Autoconf an eensy bit faster in the normal case. m4_init loads m4sugar/recursive.m4 if it detects M4 1.6.x or later. - Since we now require M4 1.4.8 or later, some compatibility glue for M4 1.4.[67] is removed from m4_init. - AT_CHECK_M4SUGAR and AT_CHECK_M4SUGAR_TEXT now, by default, run their test twice, once with the recursive implementations forced and once with the foreach implementations forced, and fails the overall test if the results are not identical. I was expecting this to expose more mismatches between the two implementations but it did not. Currently I don’t think it’s necessary to do this for any of the other tests, but I could have missed something. Only two subtests need to alter the new default: both are specifically testing for linear performance with large argument lists. Each of these is split from its parent test and divided into two new tests, one which compares the foreach implementation to the *default* implementation and runs unconditionally, and a second which tests only the recursive implementation and is skipped with M4 1.4.x. Both pairs of tests have constant expected output, so we still get a cross-check between recursive and foreach when M4 1.6.x is used. - To make it easy for AT_CHECK_M4SUGAR to force one implementation or the other, there is now an internal override macro that can be defined before invoking m4_init. It only affects the choice of recursive.m4 versus foreach.m4, not any of the other things m4_init does if it detects M4 1.6.x. - autom4te encapsulates use of the override macro using --language: --language m4sugar-recursive-iteration will always use the recursive implementations and --language m4sugar-foreach-iteration will always use the foreach implementations. (There are no equivalents of these for any of the other languages built on top of m4sugar.) - To make *that* possible, autom4te grows support for -D and -U on its own command line, which are directly mapped to -D and -U on m4’s command line. This should be the only user visible change in the patch. I did not test this patch with M4 1.6.x or 2.x, but I *did* locally comment out the AT_SKIP_IFs on the tests that are expected to be slow with M4 1.4.x. They pass (providing more evidence that both implementations are now in sync) but they take several minutes each to run on my 2018-era workstation (verifying that foreach.m4 is still necessary). * lib/m4sugar/m4sugar.m4 (m4_bmatch, _m4_bpatsubsts, m4_case, _m4_cond) (m4_do, m4_dquote_elt, _m4_foreach, m4_join, _m4_list_cmp) (_m4_list_cmp_1, _m4_list_cmp_2, _m4_list_cmp_raw, m4_map_args_pair) (_m4_minmax, m4_reverse, _m4_set_add_all_check, _m4_set_add_all_clean) (_m4_shiftn): Move definition to lib/m4sugar/recursive.m4. (m4_cr_all): Use _m4_for instead of m4_for in the definition. (m4_min): Definition is textually identical to m4_max, so m4_copy it instead of repeating it. (m4_init): Instead of including m4sugar/foreach.m4 if __m4_version__ is not defined, include m4sugar/recursive.m4 if that macro *is* defined. However, if __m4sugar_use_iteration is defined to either ‘recursive’ or ‘foreach’, then use that implementation regardless of __m4_version__. Remove backward compatibility code for M4 <1.4.8. (top level): Include m4sugar/foreach.m4 at freeze time. (throughout): Update comments related to above changes. * lib/m4sugar/recursive.m4: New file containing the recursive versions of each m4sugar macro that has a non-recursive version in foreach.m4. Each macro defined in this file should also be defined in foreach.m4 and vice versa, except for subroutines used only by one or the other. * lib/m4sugar/foreach.m4: Update comments throughout. No code changes. * lib/local.mk: Install lib/m4sugar/recursive.m4. * lib/autom4te.in: Add new languages 'm4sugar-recursive-iteration' and 'm4sugar-foreach-iteration', marked as intended for testing only. These are the same as the plain 'm4sugar' language except that __m4sugar_use_iteration is predefined to 'recursive' or 'foreach' respectively. * bin/autom4te.in: Recognize command line options -D/--define and -U/--undefine and pass them along to M4, preserving their order relative to each other and relative to .m4(f) files. * doc/autoconf.texi: Document new autom4te options. * tests/local.at (AT_PREPARE): Set shell variable at_mfour_slow_recursion to ':' if M4 doesn't define __m4_version__, or to 'false' if it does. Log the choice. (_AT_CHECK_M4SUGAR, __AT_CHECK_M4SUGAR): New helper macros. (AT_CHECK_M4SUGAR): New fifth argument, VARIANTS, expected to be a space-separated list of keywords 'default', 'recursive', and/or 'foreach'; defaults to 'recursive foreach'. Run the entire test repeatedly, once for each specified variant, with identical expectations for each. In addition, expect the generated script to be identical for each variant tested. (AT_CHECK_M4SUGAR_TEXT): Move here from m4sugar.at; reimplement without using '-o -'; add same fifth argument as was added to AT_CHECK_M4SUGAR. * tests/m4sugar.at (AT_CHECK_M4SUGAR_TEXT): Moved to local.at. (m4st_long_iteration_script, m4st_long_iteration_output) (m4st_set_stress_script, m4st_set_stress_output): New helper macros. (recursion test): Split into two tests, now called “iteration macros, long lists (foreach/default)” and “iteration macros, long lists (recursive)”. Test the recursive implementation only if M4 >=1.6 is detected. (m4_set test): Similarly for the final subtest. (throughout): Do not use '-o -' in AT_CHECK_M4SUGAR tests. Redo subtests using AT_CHECK_M4SUGAR_TEXT when possible. --- NEWS | 8 +- bin/autom4te.in | 38 ++++- bin/autoupdate.in | 2 +- doc/autoconf.texi | 28 ++++ lib/autom4te.in | 22 +++ lib/local.mk | 1 + lib/m4sugar/foreach.m4 | 98 +++++++------ lib/m4sugar/m4sugar.m4 | 301 ++++++++++++++------------------------ lib/m4sugar/recursive.m4 | 245 +++++++++++++++++++++++++++++++ tests/local.at | 119 ++++++++++++++- tests/m4sugar.at | 305 ++++++++++++++++++--------------------- 11 files changed, 756 insertions(+), 411 deletions(-) create mode 100644 lib/m4sugar/recursive.m4 diff --git a/NEWS b/NEWS index 75d2eef4..1e620e40 100644 --- a/NEWS +++ b/NEWS @@ -25,9 +25,15 @@ GNU Autoconf NEWS - User visible changes. ** New features *** Programs now recognize #elifdef and #elifndef. - The autom4te, autoscan and ifnames programs now recognize the two + The autom4te, autoscan and ifnames programs recognize these new preprocessor directives, which were introduced in C23 and C++23. +*** autom4te now supports defining M4 macros on the command line. + The autom4te program now recognizes the command line options + --define (-D) and --undefine (-U), and passes them along to M4. + This feature was added for internal testing purposes but may be + generally useful. + ** Notable bug fixes *** AC_FUNC_STRNLEN now detects Android 5.0's broken strnlen. diff --git a/bin/autom4te.in b/bin/autom4te.in index 48377cb9..b5d48d0e 100644 --- a/bin/autom4te.in +++ b/bin/autom4te.in @@ -181,6 +181,10 @@ Library directories: -B, --prepend-include=DIR prepend directory DIR to search path -I, --include=DIR append directory DIR to search path +Macro adjustments: + -D, --define=MACRO[=VALUE] define MACRO to VALUE, or empty + -U, --undefine=MACRO undefine MACRO + Tracing: -t, --trace=MACRO[:FORMAT] report the MACRO invocations -p, --preselect=MACRO prepare to trace MACRO in a future run @@ -287,7 +291,7 @@ sub files_to_options (@) foreach my $file (@file) { my $arg = shell_quote ($file); - if ($file =~ /\.m4f$/) + if ($file !~ /^-/ && $file =~ /\.m4f$/) { $arg = "--reload-state=$arg"; # If the user downgraded M4 from 1.6 to 1.4.x after freezing @@ -473,10 +477,36 @@ Try '$me --help' for more information." # files, so we use 'find_file' here. Try to get a canonical name, # as it's part of the key for caching. And some files are optional # (also handled by 'find_file'). + # + # -D/--define and -U/--undefine options are left in @ARGV because + # their relative order with actual files must be preserved; + # canonicalize and quote them here. my @argv; + my $expecting = ""; foreach (@ARGV) { - if ($_ eq '-') + if ($expecting) + { + push @argv, "${expecting}=$_"; + $expecting = ""; + } + elsif (/^-(?:D=?|-define=)(.*)$/) + { + push @argv, "--define=$1"; + } + elsif (/^-(?:D|define)/) + { + $expecting = "--define"; + } + elsif (/^-(?:U=?|-undefine=)(.*)$/) + { + push @argv, "--define=$1" + } + elsif (/^-(?:U|-undefine)/) + { + $expecting = "--undefine"; + } + elsif ($_ eq '-') { push @argv, $_; } @@ -501,6 +531,10 @@ Try '$me --help' for more information." if $file; } } + + fatal "option '$expecting' requires an argument" + if $expecting; + @ARGV = @argv; } diff --git a/bin/autoupdate.in b/bin/autoupdate.in index c16f53d4..f0e10c80 100644 --- a/bin/autoupdate.in +++ b/bin/autoupdate.in @@ -156,7 +156,7 @@ sub handle_autoconf_macros () { $au_macros{$macro} = 1; } - elsif ($file =~ /(^|\/)m4sugar\/(m4sugar|version)\.m4$/) + elsif ($file =~ /(^|\/)m4sugar\/(m4sugar|foreach|recursive|version)\.m4$/) { # Add the m4sugar macros to m4_builtins. $m4_builtins{$macro} = 1; diff --git a/doc/autoconf.texi b/doc/autoconf.texi index dc07da5d..95071097 100644 --- a/doc/autoconf.texi +++ b/doc/autoconf.texi @@ -11575,6 +11575,34 @@ autom4te Invocation Some categories of warnings are on by default. Again, for details see @ref{m4_warn}. +@item --define=@var{macro}[=@var{value}] +@itemx -D@var{macro}[=@var{value}] +Define @var{macro} to expand to @var{value}. If @var{value} is omitted, +the macro is defined with an empty expansion. + +The effects of this option are visible only to files that appear after +the option on the command line. For instance, this command + +@example +autom4te 1.m4 --define=PART=bolt 2.m4 +@end example + +causes the macro @code{PART} to have a definition in @file{2.m4} but not +in @file{1.m4}. + +@item --undefine=@var{macro} +@itemx -U@var{macro} +Undefine the macro @var{macro}. Like @option{--define}, the effects +are visible only to files that appear after the option on the command +line. Continuing the previous example, this command + +@example +autom4te 1.m4 --define=PART=bolt 2.m4 --undefine=PART 3.m4 +@end example + +causes the macro @code{PART} to have a definition in @file{2.m4} but +not in @file{1.m4} or @file{3.m4}. + @item --melt @itemx -M Do not use frozen files. Any argument @code{@var{file}.m4f} is diff --git a/lib/autom4te.in b/lib/autom4te.in index 80755be3..264a0951 100644 --- a/lib/autom4te.in +++ b/lib/autom4te.in @@ -171,3 +171,25 @@ begin-language: "M4sugar" args: --prepend-include '@pkgdatadir@' args: m4sugar/m4sugar.m4f end-language: "M4sugar" + +## ---------------------------------- ## +## M4sugar using recursive iteration, ## +## regardless of efficiency. ## +## For testing purposes only. ## +## ---------------------------------- ## + +begin-language: "M4sugar-recursive-iteration" +args: --language M4sugar +args: -D__m4sugar_use_iteration=recursive +end-language: "M4sugar-recursive-iteration" + +## ---------------------------------- ## +## M4sugar using m4_for iteration, ## +## regardless of efficiency. ## +## For testing purposes only. ## +## ---------------------------------- ## + +begin-language: "M4sugar-foreach-iteration" +args: --language M4sugar +args: -D__m4sugar_use_iteration=foreach +end-language: "M4sugar-foreach-iteration" diff --git a/lib/local.mk b/lib/local.mk index 3ae9fc2a..ff77040c 100644 --- a/lib/local.mk +++ b/lib/local.mk @@ -141,6 +141,7 @@ m4sugarlibdir = $(pkgdatadir)/m4sugar dist_m4sugarlib_DATA = \ lib/m4sugar/m4sugar.m4 \ lib/m4sugar/foreach.m4 \ + lib/m4sugar/recursive.m4 \ lib/m4sugar/m4sh.m4 nodist_m4sugarlib_DATA = \ diff --git a/lib/m4sugar/foreach.m4 b/lib/m4sugar/foreach.m4 index e2f512ee..3226d993 100644 --- a/lib/m4sugar/foreach.m4 +++ b/lib/m4sugar/foreach.m4 @@ -1,9 +1,5 @@ # -*- Autoconf -*- -# This file is part of Autoconf. -# foreach-based replacements for recursive functions. -# Speeds up GNU M4 1.4.x by avoiding quadratic $@ recursion, but penalizes -# GNU M4 1.6 by requiring more memory and macro expansions. -# +# m4_for based implementations of M4sugar functions that iterate over $@. # Copyright (C) 2008-2017, 2020-2024 Free Software Foundation, Inc. # This file is part of Autoconf. This program is free @@ -28,6 +24,13 @@ # Written by Eric Blake. +# The macros in this file avoid quadratic memory and time costs in GNU +# M4 1.4.x but are less efficient in GNU M4 1.6 than the alternatives +# in recursive.m4. +# +# Every macro in this file is also defined in recursive.m4. +# Please take care to keep the implementations in sync. + # In M4 1.4.x, every byte of $@ is rescanned. This means that an # algorithm on n arguments that recurses with one less argument each # iteration will scan n * (n + 1) / 2 arguments, for O(n^2) time. In @@ -50,11 +53,11 @@ # POSIX; although all versions of m4 1.4.x support this meaning, a # future m4 version may switch to take it as the first argument # concatenated with a literal 0, so the implementations in this file -# are not future-proof. Thus, this file is conditionally included as -# part of m4_init(), only when it is detected that M4 probably has -# quadratic behavior (ie. it lacks the macro __m4_version__). +# are not future-proof. # -# Please keep this file in sync with m4sugar.m4. +# m4sugar.m4 selects which implementation to use based on the presence +# or absence of the macro __m4_version__, which was introduced in M4 1.6.x. + # _m4_foreach(PRE, POST, IGNORED, ARG...) # --------------------------------------- @@ -80,6 +83,7 @@ m4_define([_m4_foreach], m4_define([_m4_foreach_], [[$$1[$$3]$$2[]]]) + # m4_case(SWITCH, VAL1, IF-VAL1, VAL2, IF-VAL2, ..., DEFAULT) # ----------------------------------------------------------- # Find the first VAL that SWITCH matches, and expand the corresponding @@ -103,6 +107,7 @@ m4_define([_m4_case_], m4_define([_m4_case__], [[[$$1],[$$2],[$$3],]]) + # m4_bmatch(SWITCH, RE1, VAL1, RE2, VAL2, ..., DEFAULT) # ----------------------------------------------------- # m4 equivalent of @@ -139,16 +144,13 @@ m4_define([_m4_bmatch__], [[_m4_b([$$1], [$$2], [$$3])]]) -# m4_cond(TEST1, VAL1, IF-VAL1, TEST2, VAL2, IF-VAL2, ..., [DEFAULT]) +# _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. +# Similar to m4_if, except that each TEST is expanded only if no previous +# TESTs matched. This is more efficient when the TESTs are expensive. # -# m4_cond already guarantees either 3*n or 3*n + 1 arguments, 1 <= n. -# We only have to speed up _m4_cond, by building the temporary _m4_c: +# Caller guarantees 3*n or 3*n + 1 arguments, 1 <= n. +# We build a temporary macro _m4_c: # m4_define([_m4_c], _m4_defn([m4_unquote]))_m4_c([m4_if(($1), [($2)], # [[$3]m4_define([_m4_c])])])_m4_c([m4_if(($4), [($5)], # [[$6]m4_define([_m4_c])])])..._m4_c([m4_if(($m-2), [($m-1)], @@ -166,18 +168,19 @@ m4_define([_m4_cond_], m4_define([_m4_cond__], [[_m4_c([m4_if(($$1), [($$2)], [[$$3]m4_define([_m4_c])])])]]) -# m4_bpatsubsts(STRING, RE1, SUBST1, RE2, SUBST2, ...) + +# _m4_bpatsubsts(STRING, RE1, SUBST1, RE2, SUBST2, ...) # ---------------------------------------------------- # m4 equivalent of # -# $_ = STRING; +# $_ = "[STRING]"; # s/RE1/SUBST1/g; # s/RE2/SUBST2/g; # ... +# $_ = substr $_, 1, -1; # -# m4_bpatsubsts already validated an odd number of arguments; we only -# need to speed up _m4_bpatsubsts. To avoid nesting, we build the -# temporary _m4_p: +# m4_bpatsubsts already validated an odd number of arguments. +# To avoid nesting, we build the temporary _m4_p: # m4_define([_m4_p], [$1])m4_define([_m4_p], # m4_bpatsubst(m4_dquote(_m4_defn([_m4_p])), [$2], [$3]))m4_define([_m4_p], # m4_bpatsubst(m4_dquote(_m4_defn([_m4_p])), [$4], [$5]))m4_define([_m4_p],... @@ -195,12 +198,13 @@ m4_define([_m4_bpatsubsts__], [[m4_define([_m4_p], m4_bpatsubst(m4_dquote(_m4_defn([_m4_p])), [$$1], [$$2]))]]) -# m4_shiftn(N, ...) + +# _m4_shiftn(N, ...) # ----------------- # Returns ... shifted N times. Useful for recursive "varargs" constructs. # -# m4_shiftn already validated arguments; we only need to speed up -# _m4_shiftn. If N is 3, then we build the temporary _m4_s, defined as +# m4_shiftn already validated arguments. +# If N is 3, then we build the temporary _m4_s, defined as # ,[$5],[$6],...,[$m]_m4_popdef([_m4_s]) # before calling m4_shift(_m4_s($@)). m4_define([_m4_shiftn], @@ -208,6 +212,7 @@ m4_define([_m4_shiftn], _m4_for(m4_eval([$1 + 2]), [$#], [1], [[,]m4_dquote($], [)])[_m4_popdef([_m4_s])])m4_shift(_m4_s($@))])]) + # m4_do(STRING, ...) # ------------------ # This macro invokes all its arguments (in sequence, of course). It is @@ -221,14 +226,14 @@ m4_define([m4_do], [m4_pushdef([_$0], _m4_for([1], [$#], [1], [$], [[[]]])[_m4_popdef([_$0])])_$0($@)])]) + # m4_dquote_elt(ARGS) # ------------------- # Return ARGS as an unquoted list of double-quoted arguments. -# -# _m4_foreach to the rescue. m4_define([m4_dquote_elt], [m4_if([$#], [0], [], [[[$1]]_m4_foreach([,m4_dquote(], [)], $@)])]) + # m4_reverse(ARGS) # ---------------- # Output ARGS in reverse order. @@ -269,6 +274,7 @@ m4_define([_m4_map_args_pair__], m4_define([_m4_map_args_pair_end], [m4_if(m4_eval([$3 & 1]), [1], [[m4_default([$$2], [$$1])([$$3])[]]])]) + # m4_join(SEP, ARG1, ARG2...) # --------------------------- # Produce ARG1SEPARG2...SEPARGn. Avoid back-to-back SEP when a given ARG @@ -276,7 +282,7 @@ m4_define([_m4_map_args_pair_end], # # Use a self-modifying separator, since we don't know how many # arguments might be skipped before a separator is first printed, but -# be careful if the separator contains $. _m4_foreach to the rescue. +# be careful if the separator contains $. m4_define([m4_join], [m4_pushdef([_m4_sep], [m4_define([_m4_sep], _m4_defn([m4_echo]))])]dnl [_m4_foreach([_$0([$1],], [)], $@)_m4_popdef([_m4_sep])]) @@ -284,22 +290,23 @@ m4_define([m4_join], m4_define([_m4_join], [m4_if([$2], [], [], [_m4_sep([$1])[$2]])]) + # m4_joinall(SEP, ARG1, ARG2...) # ------------------------------ # Produce ARG1SEPARG2...SEPARGn. An empty ARG results in back-to-back SEP. # No expansion is performed on SEP or ARGs. # -# A bit easier than m4_join. _m4_foreach to the rescue. +# A bit easier than m4_join. m4_define([m4_joinall], [[$2]m4_if(m4_eval([$# <= 2]), [1], [], [_m4_foreach([[$1]], [], m4_shift($@))])]) -# m4_list_cmp(A, B) -# ----------------- -# Compare the two lists of integer expressions A and B. + +# _m4_list_cmp_raw(A, B) +# ---------------------- +# Compare the two lists of integer expressions A and B, which are +# safe to expand multiple times. # -# m4_list_cmp takes care of any side effects; we only override -# _m4_list_cmp_raw, where we can safely expand lists multiple times. # First, insert padding so that both lists are the same length; the # trailing +0 is necessary to handle a missing list. Next, create a # temporary macro to perform pairwise comparisons until an inequality @@ -329,27 +336,26 @@ m4_define([_m4_list_cmp__], [[m4_eval([($$1) != ($$2)]), [1], [m4_cmp([$$1], [$$2])], ]]) -# m4_max(EXPR, ...) -# m4_min(EXPR, ...) + +# _m4_minmax(METHOD, ARG1, ARG2...) # ----------------- -# Return the decimal value of the maximum (or minimum) in a series of -# integer expressions. +# Common iteration code for m4_max and m4_min. METHOD must be _m4_max +# or _m4_min, and there must be at least two arguments to combine. # -# _m4_foreach to the rescue; we only need to replace _m4_minmax. Here, -# we need a temporary macro to track the best answer so far, so that +# We use a temporary macro to track the best answer so far, so that # the foreach expression is tractable. m4_define([_m4_minmax], [m4_pushdef([_m4_best], m4_eval([$2]))_m4_foreach( [m4_define([_m4_best], $1(_m4_best,], [))], m4_shift($@))]dnl [_m4_best[]_m4_popdef([_m4_best])]) -# m4_set_add_all(SET, VALUE...) + +# _m4_set_add_all_clean(SET, VALUE...) +# _m4_set_add_all_check(SET, VALUE...) # ----------------------------- -# Add each VALUE into SET. This is O(n) in the number of VALUEs, and -# can be faster than calling m4_set_add for each VALUE. -# -# _m4_foreach to the rescue. If no deletions have occurred, then -# avoid the speed penalty of m4_set_add. +# Iteration helpers for m4_set_add_all; the check variant is slower +# but handles the case where an element has previously been removed +# but not pruned. m4_define([_m4_set_add_all_clean], [m4_if([$#], [2], [], [_m4_foreach([_m4_set_add_clean([$1],], [, [-])], m4_shift($@))])]) diff --git a/lib/m4sugar/m4sugar.m4 b/lib/m4sugar/m4sugar.m4 index c722a830..1021eb6b 100644 --- a/lib/m4sugar/m4sugar.m4 +++ b/lib/m4sugar/m4sugar.m4 @@ -398,13 +398,7 @@ m4_define([m4_ifndef], # All the values are optional, and the macro is robust to active # symbols properly quoted. # -# Please keep foreach.m4 in sync with any adjustments made here. -m4_define([m4_case], -[m4_if([$#], 0, [], - [$#], 1, [], - [$#], 2, [$2], - [$1], [$2], [$3], - [$0([$1], m4_shift3($@))])]) +# Has multiple definitions, see foreach.m4 and recursive.m4. # m4_bmatch(SWITCH, RE1, VAL1, RE2, VAL2, ..., DEFAULT) @@ -423,13 +417,8 @@ m4_define([m4_case], # All the values are optional, and the macro is robust to active symbols # properly quoted. # -# Please keep foreach.m4 in sync with any adjustments made here. -m4_define([m4_bmatch], -[m4_if([$#], 0, [m4_fatal([$0: too few arguments: $#])], - [$#], 1, [m4_fatal([$0: too few arguments: $#: $1])], - [$#], 2, [$2], - [m4_if(m4_bregexp([$1], [$2]), -1, [$0([$1], m4_shift3($@))], - [$3])])]) +# Has multiple definitions, see foreach.m4 and recursive.m4. + # m4_argn(N, ARGS...) # ------------------- @@ -469,14 +458,16 @@ 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. +# Similar to m4_if: if the expansion of TESTn matches the string VALn, +# the overall expression expands to IF-VALn. If none of the TEST/VAL +# pairs match, the result is DEFAULT. The differences from m4_if are +# that each TEST is expanded a second time during the evaluation of +# m4_cond (so, normally, you should quote each TEST, unlike m4_if) and +# that second expansion happens only if no previous TEST/VAL pairs +# matched. Thus, m4_cond provides "short circuit" behavior: expensive +# TESTs will not be evaluated unless necessary. # # For an example, consider a previous implementation of _AS_QUOTE_IFELSE: # @@ -500,19 +491,13 @@ m4_define([_m4_cdr], # In the common case of $1 with no backslash, only one m4_index expansion # occurs, and m4_eval is avoided altogether. # -# Please keep foreach.m4 in sync with any adjustments made here. +# _m4_cond has multiple definitions, see foreach.m4 and recursive.m4. m4_define([m4_cond], [m4_if([$#], [0], [m4_fatal([$0: cannot be called without arguments])], [$#], [1], [$1], m4_eval([$# % 3]), [2], [m4_fatal([$0: missing an argument])], [_$0($@)])]) -m4_define([_m4_cond], -[m4_if(($1), [($2)], [$3], - [$#], [3], [], - [$#], [4], [$4], - [$0(m4_shift3($@))])]) - ## ---------------------------------------- ## ## 6. Enhanced version of some primitives. ## @@ -520,34 +505,35 @@ m4_define([_m4_cond], # m4_bpatsubsts(STRING, RE1, SUBST1, RE2, SUBST2, ...) # ---------------------------------------------------- -# m4 equivalent of +# m4 moral equivalent of # -# $_ = STRING; +# $_ = "[$STRING]"; # s/RE1/SUBST1/g; # s/RE2/SUBST2/g; # ... +# $_ = substr $_, 1, -1; # # All the values are optional, and the macro is robust to active symbols # properly quoted. # -# I would have liked to name this macro 'm4_bpatsubst', unfortunately, -# due to quotation problems, I need to double quote $1 below, therefore -# the anchors are broken :( I can't let users be trapped by that. +# Double quotation of STRING, which is necessary for robustness, +# is visible to the regular expressions, meaning most importantly +# that string boundary anchors (^ and $) do not work as expected :( +# Therefore this macro is not a drop-in replacement for ordinary +# 'm4_bpatsubst' and has a different name. # -# Recall that m4_shift3 always results in an argument. Hence, we need -# to distinguish between a final deletion vs. ending recursion. +# Recall that m4_shift3 always results in an argument. This +# necessitates the trailing m4_if(m4_eval()) construct, which +# distinguishes a final deletion (i.e. SUBSTn was []) from the +# end of the arguments. # -# Please keep foreach.m4 in sync with any adjustments made here. +# _m4_bpatsubsts has multiple definitions, see foreach.m4 and recursive.m4. m4_define([m4_bpatsubsts], [m4_if([$#], 0, [m4_fatal([$0: too few arguments: $#])], [$#], 1, [m4_fatal([$0: too few arguments: $#: $1])], [$#], 2, [m4_unquote(m4_builtin([patsubst], [[$1]], [$2]))], [$#], 3, [m4_unquote(m4_builtin([patsubst], [[$1]], [$2], [$3]))], [_$0($@m4_if(m4_eval($# & 1), 0, [,]))])]) -m4_define([_m4_bpatsubsts], -[m4_if([$#], 2, [$1], - [$0(m4_builtin([patsubst], [[$1]], [$2], [$3]), - m4_shift3($@))])]) # m4_copy(SRC, DST) @@ -714,26 +700,10 @@ m4_define([m4_popdef], # ----------------- # Returns ... shifted N times. Useful for recursive "varargs" constructs. # -# Autoconf does not use this macro, because it is inherently slower than -# calling the common cases of m4_shift2 or m4_shift3 directly. But it -# might as well be fast for other clients, such as Libtool. One way to -# do this is to expand $@ only once in _m4_shiftn (otherwise, for long -# lists, the expansion of m4_if takes twice as much memory as what the -# list itself occupies, only to throw away the unused branch). The end -# result is strictly equivalent to -# m4_if([$1], 1, [m4_shift(,m4_shift(m4_shift($@)))], -# [_m4_shiftn(m4_decr([$1]), m4_shift(m4_shift($@)))]) -# but with the final 'm4_shift(m4_shift($@)))' shared between the two -# paths. The first leg uses a no-op m4_shift(,$@) to balance out the (). -# -# Please keep foreach.m4 in sync with any adjustments made here. +# _m4_shiftn has multiple definitions, see foreach.m4 and recursive.m4. m4_define([m4_shiftn], [m4_assert(0 < $1 && $1 < $#)_$0($@)]) -m4_define([_m4_shiftn], -[m4_if([$1], 1, [m4_shift(], - [$0(m4_decr([$1])]), m4_shift(m4_shift($@)))]) - # m4_shift2(...) # m4_shift3(...) # -------------- @@ -758,12 +728,11 @@ m4_define([_m4_shift3], # m4_undefine(NAME) # ----------------- -# Like the original, except guarantee a warning when using something which is -# undefined (unlike M4 1.4.x). +# Like the primitive, except guarantee a warning when NAME is already +# undefined. Needed only for M4 1.4.x (see m4_init). # # This macro is called frequently, so minimize the amount of additional -# expansions by skipping m4_ifndef. Better yet, if __m4_version__ exists, -# (added in M4 1.6), then let m4 do the job for us (see m4_init). +# expansions by skipping m4_ifndef. m4_define([m4_undefine], [m4_if([$#], [0], [[$0]], [$#], [1], [m4_ifdef([$1], [_m4_undefine([$1])], @@ -844,11 +813,7 @@ m4_define([_m4_curry], [[$1])]) # unnecessary dnl's and have the macros indented properly. No concatenation # occurs after a STRING; use m4_unquote(m4_join(,STRING)) for that. # -# Please keep foreach.m4 in sync with any adjustments made here. -m4_define([m4_do], -[m4_if([$#], 0, [], - [$#], 1, [$1[]], - [$1[]$0(m4_shift($@))])]) +# Has multiple definitions, see foreach.m4 and recursive.m4. # m4_dquote(ARGS) @@ -861,11 +826,7 @@ m4_define([m4_dquote], [[$@]]) # ------------------- # Return ARGS as an unquoted list of double-quoted arguments. # -# Please keep foreach.m4 in sync with any adjustments made here. -m4_define([m4_dquote_elt], -[m4_if([$#], [0], [], - [$#], [1], [[[$1]]], - [[[$1]],$0(m4_shift($@))])]) +# Has multiple definitions, see foreach.m4 and recursive.m4. # m4_echo(ARGS) @@ -987,10 +948,7 @@ m4_define([_m4_quote], # ---------------- # Output ARGS in reverse order. # -# Please keep foreach.m4 in sync with any adjustments made here. -m4_define([m4_reverse], -[m4_if([$#], [0], [], [$#], [1], [[$1]], - [$0(m4_shift($@)), [$1]])]) +# Has multiple definitions, see foreach.m4 and recursive.m4. # m4_unquote(ARGS) @@ -1151,22 +1109,13 @@ m4_define([_m4_for], # requires swapping the argument order in the helper), insert an ignored # third argument, and use m4_shift3 to detect when recursion is complete, # at which point this looks very much like m4_map_args. +# +# _m4_foreach has multiple definitions, see foreach.m4 and recursive.m4. m4_define([m4_foreach], [m4_if([$2], [], [], [m4_pushdef([$1])_$0([m4_define([$1],], [)$3], [], $2)m4_popdef([$1])])]) -# _m4_foreach(PRE, POST, IGNORED, ARG...) -# --------------------------------------- -# Form the common basis of the m4_foreach and m4_map macros. For each -# ARG, expand PRE[ARG]POST[]. The IGNORED argument makes recursion -# easier, and must be supplied rather than implicit. -# -# Please keep foreach.m4 in sync with any adjustments made here. -m4_define([_m4_foreach], -[m4_if([$#], [3], [], - [$1[$4]$2[]$0([$1], [$2], m4_shift3($@))])]) - # m4_foreach_w(VARIABLE, LIST, EXPRESSION) # ---------------------------------------- @@ -1259,14 +1208,7 @@ m4_define([m4_map_args], # => (c,d) # => (e) # -# Please keep foreach.m4 in sync with any adjustments made here. -m4_define([m4_map_args_pair], -[m4_if([$#], [0], [m4_fatal([$0: too few arguments: $#])], - [$#], [1], [m4_fatal([$0: too few arguments: $#: $1])], - [$#], [2], [], - [$#], [3], [m4_default([$2], [$1])([$3])[]], - [$#], [4], [$1([$3], [$4])[]], - [$1([$3], [$4])[]$0([$1], [$2], m4_shift(m4_shift3($@)))])]) +# Has multiple definitions, see foreach.m4 and recursive.m4. # m4_map_args_sep([PRE], [POST], [SEP], ARG...) @@ -2206,9 +2148,11 @@ m4_defn([m4_cr_digits])dnl # in places where m4_translit is faster than an equivalent m4_bpatsubst; # the regex '[^a-z]' is equivalent to: # m4_translit(m4_dquote(m4_defn([m4_cr_all])), [a-z]) +# +# Note: m4_for is not yet available. m4_define([m4_cr_all], -m4_translit(m4_dquote(m4_format(m4_dquote(m4_for( - ,1,255,,[[%c]]))m4_for([i],1,255,,[,i]))), [$*-], [*$])-) +m4_translit(m4_dquote(m4_format(m4_dquote( + _m4_for(1,255,1,[m4_ignore(],[)[%c]]))_m4_for(1,255,1,[,]))), [$*-], [*$])-) # _m4_define_cr_not(CATEGORY) @@ -2439,33 +2383,15 @@ note: 'dn@&t@l' is a macro]))])dnl # Produce ARG1SEPARG2...SEPARGn. Avoid back-to-back SEP when a given ARG # is the empty string. No expansion is performed on SEP or ARGs. # -# Since the number of arguments to join can be arbitrarily long, we -# want to avoid having more than one $@ in the macro definition; -# otherwise, the expansion would require twice the memory of the already -# long list. Hence, m4_join merely looks for the first non-empty element, -# and outputs just that element; while _m4_join looks for all non-empty -# elements, and outputs them following a separator. The final trick to -# note is that we decide between recursing with $0 or _$0 based on the -# nested m4_if ending with '_'. -# -# Please keep foreach.m4 in sync with any adjustments made here. -m4_define([m4_join], -[m4_if([$#], [1], [], - [$#], [2], [[$2]], - [m4_if([$2], [], [], [[$2]_])$0([$1], m4_shift2($@))])]) -m4_define([_m4_join], -[m4_if([$#$2], [2], [], - [m4_if([$2], [], [], [[$1$2]])$0([$1], m4_shift2($@))])]) +# Has multiple definitions, see foreach.m4 and recursive.m4. + # m4_joinall(SEP, ARG1, ARG2...) # ------------------------------ # Produce ARG1SEPARG2...SEPARGn. An empty ARG results in back-to-back SEP. # No expansion is performed on SEP or ARGs. # -# Please keep foreach.m4 in sync with any adjustments made here. -m4_define([m4_joinall], [[$2]_$0([$1], m4_shift($@))]) -m4_define([_m4_joinall], -[m4_if([$#], [2], [], [[$1$3]$0([$1], m4_shift2($@))])]) +# Has multiple definitions, see foreach.m4 and recursive.m4. # m4_combine([SEPARATOR], PREFIX-LIST, [INFIX], SUFFIX...) # -------------------------------------------------------- @@ -2758,8 +2684,10 @@ m4_define([m4_cmp], # m4_list_cmp(A, B) # ----------------- +# Compare the two lists of integer expressions A and B. Guarantee +# exactly one expansion of both lists' side effects. # -# Compare the two lists of integer expressions A and B. For instance: +# For instance: # m4_list_cmp([1, 0], [1]) -> 0 # m4_list_cmp([1, 0], [1, 0]) -> 0 # m4_list_cmp([1, 2], [1, 0]) -> 1 @@ -2770,43 +2698,22 @@ m4_define([m4_cmp], # m4_define([xa], [oops])dnl # m4_list_cmp([[0xa]], [5+5]) -> 0 # -# Rather than face the overhead of m4_case, we use a helper function whose -# expansion includes the name of the macro to invoke on the tail, either -# m4_ignore or m4_unquote. This is particularly useful when comparing -# long lists, since less text is being expanded for deciding when to end -# recursion. The recursion is between a pair of macros that alternate -# which list is trimmed by one element; this is more efficient than -# calling m4_cdr on both lists from a single macro. Guarantee exactly -# one expansion of both lists' side effects. -# -# Please keep foreach.m4 in sync with any adjustments made here. +# _m4_list_cmp_raw has multiple definitions, see foreach.m4 and recursive.m4. m4_define([m4_list_cmp], [_$0_raw(m4_dquote($1), m4_dquote($2))]) -m4_define([_m4_list_cmp_raw], -[m4_if([$1], [$2], [0], [_m4_list_cmp_1([$1], $2)])]) - -m4_define([_m4_list_cmp], -[m4_if([$1], [], [0m4_ignore], [$2], [0], [m4_unquote], [$2m4_ignore])]) - -m4_define([_m4_list_cmp_1], -[_m4_list_cmp_2([$2], [m4_shift2($@)], $1)]) - -m4_define([_m4_list_cmp_2], -[_m4_list_cmp([$1$3], m4_cmp([$3+0], [$1+0]))( - [_m4_list_cmp_1(m4_dquote(m4_shift3($@)), $2)])]) - # m4_max(EXPR, ...) # m4_min(EXPR, ...) # ----------------- # Return the decimal value of the maximum (or minimum) in a series of # integer expressions. # -# M4 1.4.x doesn't provide ?:. Hence this huge m4_eval. Avoid m4_eval -# if both arguments are identical, but be aware of m4_max(0xa, 10) (hence -# the use of <=, not just <, in the second multiply). +# Calls with only one or two arguments short-circuit the iteration. +# Calls with two _textually_ identical arguments can avoid m4_eval. +# The _$0 trick allows both entry points to have the same definition. # -# Please keep foreach.m4 in sync with any adjustments made here. +# _m4_minmax has multiple definitions, see foreach.m4 and recursive.m4. +# Both rely on _m4_max and _m4_min, defined below. m4_define([m4_max], [m4_if([$#], [0], [m4_fatal([too few arguments to $0])], [$#], [1], [m4_eval([$1])], @@ -2814,29 +2721,25 @@ m4_define([m4_max], [$#], [2], [_$0($@)], [_m4_minmax([_$0], $@)])]) +m4_copy([m4_max], [m4_min]) + +# _m4_max(EXPR1, EXPR2) +# _m4_min(EXPR1, EXPR2) +# --------------------- +# Return the decimal value of the maximum (minimum) of EXPR1 and EXPR2. +# Called by m4_max/m4_min, respectively, via _m4_minmax. +# +# M4 1.4.x's eval() doesn't recognize ? : so we have to fake it with +# arithmetic. The arguments could be numerically but not textually +# equal, e.g. _m4_max(10, 0xA), so the second multiply uses <=. +# (Also, the textually-identical shortcut is only used when +# m4_max/m4_min were called with exactly two arguments.) m4_define([_m4_max], [m4_eval((([$1]) > ([$2])) * ([$1]) + (([$1]) <= ([$2])) * ([$2]))]) -m4_define([m4_min], -[m4_if([$#], [0], [m4_fatal([too few arguments to $0])], - [$#], [1], [m4_eval([$1])], - [$#$1], [2$2], [m4_eval([$1])], - [$#], [2], [_$0($@)], - [_m4_minmax([_$0], $@)])]) - m4_define([_m4_min], [m4_eval((([$1]) < ([$2])) * ([$1]) + (([$1]) >= ([$2])) * ([$2]))]) -# _m4_minmax(METHOD, ARG1, ARG2...) -# --------------------------------- -# Common recursion code for m4_max and m4_min. METHOD must be _m4_max -# or _m4_min, and there must be at least two arguments to combine. -# -# Please keep foreach.m4 in sync with any adjustments made here. -m4_define([_m4_minmax], -[m4_if([$#], [3], [$1([$2], [$3])], - [$0([$1], $1([$2], [$3]), m4_shift3($@))])]) - # m4_sign(A) # ---------- @@ -2972,6 +2875,10 @@ m4_ifdef([m4_PACKAGE_VERSION], # Expand IF-UNIQ on the first addition, and IF-DUP if it is already in # the set. # +# We do not want to add a duplicate for a previously deleted but +# unpruned element, but it is just as easy to check existence directly +# as it is to query _m4_set_cleanup($1). +# # Three cases must be handled: # - _m4_set([$1],$2) is not defined: # define _m4_set([$1],$2) to 1, push $2 as a definition of _m4_set([$1]), @@ -3006,10 +2913,6 @@ m4_define([_m4_set_add_clean], # Add VALUE as an element of SET. Expand IF-UNIQ on the first # addition, and IF-DUP if it is already in the set. Addition of one # element is O(1), such that overall set creation is O(n). -# -# We do not want to add a duplicate for a previously deleted but -# unpruned element, but it is just as easy to check existence directly -# as it is to query _m4_set_cleanup($1). m4_define([m4_set_add], [_m4_set_add([$1], [$2], [_m4_set_size([$1], [m4_incr])$3], [$4])]) @@ -3018,13 +2921,8 @@ m4_define([m4_set_add], # Add each VALUE into SET. This is O(n) in the number of VALUEs, and # can be faster than calling m4_set_add for each VALUE. # -# Implement two recursion helpers; the check variant is slower but -# handles the case where an element has previously been removed but -# not pruned. The recursion helpers ignore their second argument, so -# that we can use the faster m4_shift2 and 2 arguments, rather than -# _m4_shift2 and one argument, as the signal to end recursion. -# -# Please keep foreach.m4 in sync with any adjustments made here. +# _m4_set_add_all_clean and _m4_set_add_all_check have multiple +# definitions, see foreach.m4 and recursive.m4. m4_define([m4_set_add_all], [m4_case([$#], [0], [], [1], [], [m4_define([_m4_set_size($1)], @@ -3032,14 +2930,6 @@ m4_define([m4_set_add_all], + m4_len(m4_ifdef([_m4_set_cleanup($1)], [_$0_check], [_$0_clean])([$1], $@))))])]) -m4_define([_m4_set_add_all_clean], -[m4_if([$#], [2], [], - [_m4_set_add_clean([$1], [$3], [-], [])$0([$1], m4_shift2($@))])]) - -m4_define([_m4_set_add_all_check], -[m4_if([$#], [2], [], - [_m4_set_add([$1], [$3], [-], [])$0([$1], m4_shift2($@))])]) - # m4_set_contains(SET, VALUE, [IF-PRESENT], [IF-ABSENT]) # ------------------------------------------------------ # Expand IF-PRESENT if SET contains VALUE, otherwise expand IF-ABSENT. @@ -3293,6 +3183,21 @@ m4_define([_m4_set_union], ## 16. Setting up M4sugar. ## ## ------------------------ ## +# All of our macros that iterate over $@ have two implementations. +# The implementations in foreach.m4 avoid recursing over $@, which is +# necessary to avoid quadratic space and time consumption in GNU M4 +# 1.4.x. The implementations in recursive.m4 are simpler, more +# portable, and, if used with a M4 implementation where recursion +# over $@ doesn't have quadratic costs, more efficent. GNU M4 1.6.x +# will be such an implementation. +# +# However, as of 2024, there has not yet been any official release +# from the M4 1.6.x development effort. Therefore, the foreach.m4 +# implementation is the default, and the one baked into freeze files. +# m4_init will replace it with the recursive.m4 implementation if it +# detects use of M4 1.6.x. +m4_include([m4sugar/foreach.m4]) + # _m4_divert_diversion should be defined. m4_divert_push([KILL]) @@ -3306,25 +3211,35 @@ m4_pattern_forbid([^_?m4_]) m4_pattern_forbid([^dnl$]) # If __m4_version__ is defined, we assume that we are being run by M4 -# 1.6 or newer, thus $@ recursion is linear, and debugmode(+do) -# is available for faster checks of dereferencing undefined macros -# and forcing dumpdef to print to stderr regardless of debugfile. -# But if it is missing, we assume we are being run by M4 1.4.x, that -# $@ recursion is quadratic, and that we need foreach-based -# replacement macros. Also, m4 prior to 1.4.8 loses track of location -# during m4wrap text; __line__ should never be 0. +# 1.6.x or newer, meaning: +# - debugmode([+do]) can be used, which means our wrappers around +# the primitive m4_defn, m4_dumpdef, m4_popdef, and m4_undefine +# are unnecessary +# - recursive iteration over $@ is efficient # -# Use the raw builtin to avoid tripping up include tracing. -# Meanwhile, avoid m4_copy, since it temporarily undefines m4_defn. +# For testing purposes, the choice of iteration implementation can +# be overridden by defining the macro __m4sugar_use_iteration with +# value 'foreach' or 'recursive'. Autom4te exposes this via +# --language variants; see autom4te.cfg, or lib/autom4te.in if you're +# looking at the source tree. +# +# Use the raw include builtin to avoid tripping up include tracing. +# Can't use m4_copy, since it temporarily undefines m4_defn. m4_ifdef([__m4_version__], [m4_debugmode([+do]) m4_define([m4_defn], _m4_defn([_m4_defn])) m4_define([m4_dumpdef], _m4_defn([_m4_dumpdef])) m4_define([m4_popdef], _m4_defn([_m4_popdef])) -m4_define([m4_undefine], _m4_defn([_m4_undefine]))], -[m4_builtin([include], [m4sugar/foreach.m4]) -m4_wrap_lifo([m4_if(__line__, [0], [m4_pushdef([m4_location], -]]m4_dquote(m4_dquote(m4_dquote(__file__:__line__)))[[)])])]) +m4_define([m4_undefine], _m4_defn([_m4_undefine])) +m4_define_default([__m4sugar_use_iteration], [recursive])], +[# M4 1.4.x +m4_define_default([__m4sugar_use_iteration], [foreach])]) +m4_case(m4_defn([__m4sugar_use_iteration]), + [recursive], [m4_builtin([include], [m4sugar/recursive.m4])], + [foreach], [], + [m4_fatal(m4_join([ ], + [bad value for __m4sugar_use_iteration:], + m4_defn([__m4sugar_use_iteration])))]) # Rewrite the first entry of the diversion stack. m4_divert([KILL]) diff --git a/lib/m4sugar/recursive.m4 b/lib/m4sugar/recursive.m4 new file mode 100644 index 00000000..8cc6bc4e --- /dev/null +++ b/lib/m4sugar/recursive.m4 @@ -0,0 +1,245 @@ +# -*- Autoconf -*- +# Recursive implementations of M4sugar functions that iterate over $@. +# Copyright (C) 2008-2017, 2020-2024 Free Software Foundation, Inc. +# +# This file is part of Autoconf. This program is free +# software; you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# Under Section 7 of GPL version 3, you are granted additional +# permissions described in the Autoconf Configure Script Exception, +# version 3.0, as published by the Free Software Foundation. +# +# You should have received a copy of the GNU General Public License +# and a copy of the Autoconf Configure Script Exception along with +# this program; see the files COPYINGv3 and COPYING.EXCEPTION +# respectively. If not, see <https://www.gnu.org/licenses/>. + +# The macros in this file are efficient in GNU M4 1.6 but quadratically +# expensive in GNU M4 1.4. See foreach.m4 for a detailed explanation. +# +# Every macro in this file is also defined in foreach.m4. +# Please take care to keep the implementations in sync. +# +# m4sugar.m4 selects which implementation to use based on the presence +# or absence of the macro __m4_version__, which was introduced in M4 1.6.x. + + +# _m4_foreach(PRE, POST, IGNORED, ARG...) +# --------------------------------------- +# Form the common basis of the m4_foreach and m4_map macros. For each +# ARG, expand PRE[ARG]POST[]. The IGNORED argument makes recursion +# easier, and must be supplied rather than implicit. +m4_define([_m4_foreach], +[m4_if([$#], [3], [], + [$1[$4]$2[]$0([$1], [$2], m4_shift3($@))])]) + + +# m4_case(SWITCH, VAL1, IF-VAL1, VAL2, IF-VAL2, ..., DEFAULT) +# ----------------------------------------------------------- +# Shorthand for m4_if(SWITCH, VAL1, IF-VAL1, +# SWITCH, VAL2, IF-VAL2, ..., DEFAULT). +m4_define([m4_case], +[m4_if([$#], 0, [], + [$#], 1, [], + [$#], 2, [$2], + [$1], [$2], [$3], + [$0([$1], m4_shift3($@))])]) + + +# m4_bmatch(SWITCH, RE1, VAL1, RE2, VAL2, ..., DEFAULT) +# ----------------------------------------------------- +# Match SWITCH against each of the REs. Expand to the VAL +# corresponding to the first RE that matched. (Stops evaluating +# when a match is found.) +m4_define([m4_bmatch], +[m4_if([$#], 0, [m4_fatal([$0: too few arguments: $#])], + [$#], 1, [m4_fatal([$0: too few arguments: $#: $1])], + [$#], 2, [$2], + [m4_if(m4_bregexp([$1], [$2]), -1, [$0([$1], m4_shift3($@))], + [$3])])]) + +# _m4_cond(TEST1, VAL1, IF-VAL1, TEST2, VAL2, IF-VAL2, ..., [DEFAULT]) +# ------------------------------------------------------------------- +# Similar to m4_if, except that each TEST is expanded only if no previous +# TESTs matched. This is more efficient when the TESTs are expensive. +# +# Caller guarantees 3*n or 3*n + 1 arguments, 1 <= n. +m4_define([_m4_cond], +[m4_if(($1), [($2)], [$3], + [$#], [3], [], + [$#], [4], [$4], + [$0(m4_shift3($@))])]) + +# _m4_bpatsubsts(STRING, RE1, SUBST1, RE2, SUBST2, ...) +# ---------------------------------------------------- +# m4 equivalent of +# +# $_ = "[$STRING]"; +# s/RE1/SUBST1/g; +# s/RE2/SUBST2/g; +# ... +# $_ = substr $_, 1, -1; +# +# m4_bpatsubsts already validated an odd number of arguments. +m4_define([_m4_bpatsubsts], +[m4_if([$#], 2, [$1], + [$0(m4_builtin([patsubst], [[$1]], [$2], [$3]), + m4_shift3($@))])]) + + +# _m4_shiftn(N, ...) +# ------------------ +# Returns ... shifted N times. Does not do any validation of N. +# +# Autoconf does not use this macro, because it is inherently slower than +# calling the common cases of m4_shift2 or m4_shift3 directly. But it +# might as well be fast for other clients, such as Libtool. One way to +# do this is to expand $@ only once in _m4_shiftn (otherwise, for long +# lists, the expansion of m4_if takes twice as much memory as what the +# list itself occupies, only to throw away the unused branch). The end +# result is strictly equivalent to +# m4_if([$1], 1, [m4_shift(,m4_shift(m4_shift($@)))], +# [_m4_shiftn(m4_decr([$1]), m4_shift(m4_shift($@)))]) +# but with the final 'm4_shift(m4_shift($@)))' shared between the two +# paths. The first leg uses a no-op m4_shift(,$@) to balance out the (). +m4_define([_m4_shiftn], +[m4_if([$1], 1, [m4_shift(], + [$0(m4_decr([$1])]), m4_shift(m4_shift($@)))]) + + +# m4_do(...) +# ---------- +# Expands to the expansion of each argument, in sequence, expanding an +# empty quoted string after each one (thus, m4_do([m4_], [fatal([oops])]) +# will *not* invoke m4_fatal). Useful for splitting macro definitions +# across many lines for readability. +m4_define([m4_do], +[m4_if([$#], 0, [], + [$#], 1, [$1[]], + [$1[]$0(m4_shift($@))])]) + + +# m4_dquote_elt(ARGS) +# ------------------ +# Return ARGS as an unquoted list of double-quoted arguments. +m4_define([m4_dquote_elt], +[m4_if([$#], [0], [], + [$#], [1], [[[$1]]], + [[[$1]],$0(m4_shift($@))])]) + + +# m4_reverse(ARGS) +# ---------------- +# Output ARGS in reverse order. +m4_define([m4_reverse], +[m4_if([$#], [0], [], [$#], [1], [[$1]], + [$0(m4_shift($@)), [$1]])]) + + +# m4_map_args_pair(EXPRESSION, [END-EXPR = EXPRESSION], ARG...) +# ------------------------------------------------------------- +# Perform a pairwise grouping of consecutive ARGs, by expanding +# EXPRESSION([ARG1], [ARG2]). If there are an odd number of ARGs, the +# final argument is expanded with END-EXPR([ARGn]). +m4_define([m4_map_args_pair], +[m4_if([$#], [0], [m4_fatal([$0: too few arguments: $#])], + [$#], [1], [m4_fatal([$0: too few arguments: $#: $1])], + [$#], [2], [], + [$#], [3], [m4_default([$2], [$1])([$3])[]], + [$#], [4], [$1([$3], [$4])[]], + [$1([$3], [$4])[]$0([$1], [$2], m4_shift(m4_shift3($@)))])]) + + +# m4_join(SEP, ARG1, ARG2...) +# --------------------------- +# Produce ARG1SEPARG2...SEPARGn. Avoid back-to-back SEP when a given ARG +# is the empty string. No expansion is performed on SEP or ARGs. +# +# +# Since the number of arguments to join can be arbitrarily long, we +# want to avoid having more than one $@ in the macro definition; +# otherwise, the expansion would require twice the memory of the already +# long list. Hence, m4_join merely looks for the first non-empty element, +# and outputs just that element; while _m4_join looks for all non-empty +# elements, and outputs them following a separator. The final trick to +# note is that we decide between recursing with $0 or _$0 based on the +# nested m4_if ending with '_'. +m4_define([m4_join], +[m4_if([$#], [1], [], + [$#], [2], [[$2]], + [m4_if([$2], [], [], [[$2]_])$0([$1], m4_shift2($@))])]) + +m4_define([_m4_join], +[m4_if([$#$2], [2], [], + [m4_if([$2], [], [], [[$1$2]])$0([$1], m4_shift2($@))])]) + + +# m4_joinall(SEP, ARG1, ARG2...) +# ------------------------------ +# Produce ARG1SEPARG2...SEPARGn. An empty ARG results in back-to-back SEP. +# No expansion is performed on SEP or ARGs. +m4_define([m4_joinall], +[[$2]_$0([$1], m4_shift($@))]) + +m4_define([_m4_joinall], +[m4_if([$#], [2], [], [[$1$3]$0([$1], m4_shift2($@))])]) + + +# _m4_list_cmp_raw(A, B) +# ---------------------- +# Compare the two lists of integer expressions A and B, which are +# safe to expand multiple times. +# +# Rather than face the overhead of m4_case, we use a helper function whose +# expansion includes the name of the macro to invoke on the tail, either +# m4_ignore or m4_unquote. This is particularly useful when comparing +# long lists, since less text is being expanded for deciding when to end +# recursion. The recursion is between a pair of macros that alternate +# which list is trimmed by one element; this is more efficient than +# calling m4_cdr on both lists from a single macro. +m4_define([_m4_list_cmp_raw], +[m4_if([$1], [$2], [0], [_m4_list_cmp_1([$1], $2)])]) + +m4_define([_m4_list_cmp], +[m4_if([$1], [], [0m4_ignore], [$2], [0], [m4_unquote], [$2m4_ignore])]) + +m4_define([_m4_list_cmp_1], +[_m4_list_cmp_2([$2], [m4_shift2($@)], $1)]) + +m4_define([_m4_list_cmp_2], +[_m4_list_cmp([$1$3], m4_cmp([$3+0], [$1+0]))( + [_m4_list_cmp_1(m4_dquote(m4_shift3($@)), $2)])]) + + +# _m4_minmax(METHOD, ARG1, ARG2...) +# --------------------------------- +# Common recursion code for m4_max and m4_min. METHOD must be _m4_max +# or _m4_min, and there must be at least two arguments to combine. +# +# Please keep foreach.m4 in sync with any adjustments made here. +m4_define([_m4_minmax], +[m4_if([$#], [3], [$1([$2], [$3])], + [$0([$1], $1([$2], [$3]), m4_shift3($@))])]) + + +# _m4_set_add_all_clean(SET, VALUE...) +# _m4_set_add_all_check(SET, VALUE...) +# ----------------------------- +# Recursion helpers for m4_set_add_all; the check variant is slower +# but handles the case where an element has previously been removed +# but not pruned. +m4_define([_m4_set_add_all_clean], +[m4_if([$#], [2], [], + [_m4_set_add_clean([$1], [$3], [-], [])$0([$1], m4_shift2($@))])]) + +m4_define([_m4_set_add_all_check], +[m4_if([$#], [2], [], + [_m4_set_add([$1], [$3], [-], [])$0([$1], m4_shift2($@))])]) diff --git a/tests/local.at b/tests/local.at index 4d3d4533..3dd86d29 100644 --- a/tests/local.at +++ b/tests/local.at @@ -98,6 +98,20 @@ if test "$ACLOCAL" != false; then ACLOCAL="$ACLOCAL --system-acdir=`cd at_empty_dir && pwd`" fi +# Using the same test that m4sugar.m4 uses, guess whether the +# iteration macros in m4sugar/recursive.m4 will be quadratically +# expensive. The variable storing the result of this test is called +# "at_mfour_slow_recursion" so we don't have to write an @&t@ in the +# middle of it every time we use it. When using this, keep in mind +# that the fifth argument to AT_CHECK_M4SUGAR cannot be a shell variable. +# See m4sugar.at's pairs of "foreach/default" and "recursive" tests for +# how to deal with this constraint. +if test x"`echo __m4@&t@_version__ | $M4`" = x__m4@&t@_version__; then + at_mfour_slow_recursion=: +else + at_mfour_slow_recursion=false +fi + # Determine how long we need to delay in between operations that might # modify autom4te.cache. This depends on three factors: whether the # 'sleep' utility supports fractional seconds in its argument; what @@ -220,6 +234,11 @@ do done AS_ECHO(["$at_srcdir/AT_LINE: using ${at_ts_resolution}s as mtime resolution"]) +if $at_mfour_slow_recursion; then + AS_ECHO(["$at_srcdir/AT_LINE: M4 recursion over \$@ is believed to be slow"]) +else + AS_ECHO(["$at_srcdir/AT_LINE: M4 recursion over \$@ is believed to be fast"]) +fi echo exit $status ) >&AS_MESSAGE_LOG_FD 2>&1 @@ -395,13 +414,103 @@ m4_define([AT_DATA_M4SUGAR], [\1\3\5@&t@\2\4\6])])]) -# AT_CHECK_M4SUGAR(FLAGS, [EXIT-STATUS = 0], STDOUT, STDERR) -# ---------------------------------------------------------- +# AT_CHECK_M4SUGAR(FLAGS, EXIT-STATUS = 0, STDOUT, STDERR, +# [VARIANTS = recursive foreach]) +# -------------------------------------------------------- +# VARIANTS must be a space-separated list of one or more of 'default', +# 'recursive', 'foreach'. Run autom4te on 'script.4s' for each variant, +# supplying FLAGS as additional command line flags. Expect the specified +# exit status, stdout, and stderr from each variant. In addition, the +# generated file 'script' must be identical for each tested variant. +# +# Implementation note: since there are only three variants and we need +# to do stuff in between AT_CHECK_AUTOM4TE invocations depending on which +# variants are enabled, it's easiest to not try to use a loop. m4_define([AT_CHECK_M4SUGAR], -[AT_KEYWORDS([m4sugar]) -AT_CHECK_AUTOM4TE([--language=m4sugar script.4s -o script $1], - [$2], [$3], [$4])]) +[AT_KEYWORDS([m4sugar])]dnl +[_$0([$1], [$2], [$3], [$4], m4_default([$5], [recursive foreach]))]) +# _AT_CHECK_M4SUGAR(FLAGS, EXIT-STATUS, STDOUT, STDERR, VARIANTS) +m4_define([_AT_CHECK_M4SUGAR], +[m4_bmatch([$3], [\(ignore\|stdout\|expout\)\(-nolog\)?], [], + [AT_DATA([expout], [$3]) +])]dnl +[m4_bmatch([$4], [\(ignore\|stderr\|experr\)\(-nolog\)?], [], + [AT_DATA([experr], [$4]) +])]dnl +[_$0(m4_bmatch([$5], [\<recursive\>], [recursive], []), + m4_bmatch([$5], [\<foreach\>], [foreach], []), + m4_bmatch([$5], [\<default\>], [default], []), + [$1], + [$2], + m4_bmatch([$3], [\(ignore\|stdout\|expout\)\(-nolog\)?], [$3], [expout]), + m4_bmatch([$4], [\(ignore\|stderr\|experr\)\(-nolog\)?], [$4], [experr]))]) + +# __AT_CHECK_M4SUGAR( +# $1 = TEST-RECURSIVE, +# $2 = TEST-FOREACH, +# $3 = TEST-DEFAULT, +# $4 = FLAGS, +# $5 = EXIT-STATUS = 0, +# $6 = STDOUT, +# $7 = STDERR +# ) +m4_define([__AT_CHECK_M4SUGAR], +[m4_ifnblank([$1], +[AT_CHECK_AUTOM4TE( + [--language=m4sugar-recursive-iteration $4 script.4s -o script], + [$5], [$6], [$7]) +m4_ifnblank([$2$3], +[for f in script stdout stdout-raw stderr stderr-raw; do + if test -f $f; then mv $f ${f}.ri; fi +done +])])]dnl +[m4_ifnblank([$2], +[AT_CHECK_AUTOM4TE( + [--language=m4sugar-foreach-iteration $4 script.4s -o script], + [$5], [$6], [$7]) +m4_ifnblank([$1], +[for f in script stdout stderr; do + if test -f $f; then + AT_CHECK([$at_diff ${f}.ri $f], + [], [ignore], [ignore]) + fi +done +])m4_ifnblank([$3], +[for f in script stdout stdout-raw stderr stderr-raw; do + if test -f $f; then mv $f ${f}.fi; fi +done +])])]dnl +[m4_ifnblank([$3], +[AT_CHECK_AUTOM4TE( + [--language=m4sugar $4 script.4s -o script], + [$5], [$6], [$7]) +m4_ifnblank([$1$2], +[for f in script stdout stderr; do + if test -f $f; then + AT_CHECK([$at_diff ${f}.m4_ifnblank([$1], [ri], [fi]) $f], + [0], [ignore], [ignore]) + fi +done +])])]) + + +# AT_CHECK_M4SUGAR_TEXT(CODE, STDOUT, STDERR, FLAGS, +# [VARIANTS = recursive foreach]) +# ----------------------------------------------------- +# Check that m4sugar CODE expands to STDOUT and emits STDERR. +# All other arguments are as for AT_CHECK_M4SUGAR. +m4_define([AT_CHECK_M4SUGAR_TEXT], +[AT_DATA([expscript], [$2]) +AT_DATA_M4SUGAR([script.4s], +[[m4_init +m4_divert_push([])[]dnl +]$1[[]dnl +m4_divert_pop([]) +]]) +AT_CHECK_M4SUGAR([$4], [0], [], [$3], [$5]) +AT_CHECK([$at_diff expscript script], [0], [ignore], [ignore]) +]) ## -------------- ## diff --git a/tests/m4sugar.at b/tests/m4sugar.at index 433f3b23..8eb36ed3 100644 --- a/tests/m4sugar.at +++ b/tests/m4sugar.at @@ -19,22 +19,6 @@ AT_BANNER([M4sugar.]) # along with this program. If not, see <https://www.gnu.org/licenses/>. -# AT_CHECK_M4SUGAR_TEXT(CODE, STDOUT, STDERR, FLAGS) -# -------------------------------------------------- -# Check that m4sugar CODE expands to STDOUT and emits STDERR. -m4_define([AT_CHECK_M4SUGAR_TEXT], -[ -AT_DATA_M4SUGAR([script.4s], -[[m4_init -m4_divert_push([])[]dnl -]$1[[]dnl -m4_divert_pop([]) -]]) - -AT_CHECK_M4SUGAR([$4 -o-],, [$2], [$3]) -])# AT_CHECK_M4SUGAR_TEXT - - ## ------------------ ## ## m4_stack_foreach. ## ## ------------------ ## @@ -113,35 +97,39 @@ AT_DATA_M4SUGAR([script.4s], m4_defn([good], [oops]) ]]) -AT_CHECK_M4SUGAR([-o-], 1, [], [stderr]) +AT_CHECK_M4SUGAR([], 1, [], [stderr]) AT_CHECK([grep good stderr], [1]) AT_CHECK([grep 'm4@&t@_defn: undefined.*oops' stderr], [0], [ignore]) +AT_CHECK([if test -f script; then exit 1; fi]) AT_DATA_M4SUGAR([script.4s], [[m4_define([good]) m4_popdef([good], [oops]) ]]) -AT_CHECK_M4SUGAR([-o-], 1, [], [stderr]) +AT_CHECK_M4SUGAR([], 1, [], [stderr]) AT_CHECK([grep good stderr], [1]) AT_CHECK([grep 'm4@&t@_popdef: undefined.*oops' stderr], [0], [ignore]) +AT_CHECK([if test -f script; then exit 1; fi]) AT_DATA_M4SUGAR([script.4s], [[m4_define([good]) m4_undefine([good], [oops]) ]]) -AT_CHECK_M4SUGAR([-o-], 1, [], [stderr]) +AT_CHECK_M4SUGAR([], 1, [], [stderr]) AT_CHECK([grep good stderr], [1]) AT_CHECK([grep 'm4@&t@_undefine: undefined.*oops' stderr], [0], [ignore]) +AT_CHECK([if test -f script; then exit 1; fi]) # Cannot rename an undefined macro. AT_DATA_M4SUGAR([script.4s], [[m4_rename([oops], [good]) ]]) -AT_CHECK_M4SUGAR([-o-], 1, [], [stderr]) +AT_CHECK_M4SUGAR([], 1, [], [stderr]) AT_CHECK([grep 'm4@&t@_undefine: undefined.*oops' stderr], [0], [ignore]) +AT_CHECK([if test -f script; then exit 1; fi]) # Check that pushdef stacks can be renamed. AT_CHECK_M4SUGAR_TEXT([[m4_pushdef([a], [1])dnl @@ -195,9 +183,10 @@ AT_DATA_M4SUGAR([script.4s], m4_dumpdef([good], [oops]) ]]) -AT_CHECK_M4SUGAR([-o-], 1, [], [stderr]) +AT_CHECK_M4SUGAR([], 1, [], [stderr]) AT_CHECK([grep '^good: \[[yep]]$' stderr], [0], [ignore]) AT_CHECK([grep 'm4@&t@_dumpdef: undefined.*oops' stderr], [0], [ignore]) +AT_CHECK([if test -f script; then exit 1; fi]) # Check that pushdef stacks can be dumped. AT_CHECK_M4SUGAR_TEXT([[m4_divert_push([KILL]) @@ -1136,16 +1125,14 @@ one, two, three 1 2 3 ]]) -AT_DATA_M4SUGAR([script.4s], -[[m4_init[]dnl -m4_append_uniq([str], [a], [ ]) -m4_append_uniq([str], [a b], [ ]) -m4_divert([])dnl +AT_CHECK_M4SUGAR_TEXT([[dnl +m4_append_uniq([str], [a], [ ])dnl +m4_append_uniq([str], [a b], [ ])dnl str -]]) - -AT_CHECK_M4SUGAR([-o-], 0, [[a a b -]], [[script.4s:3: warning: m4@&t@_append_uniq: 'a b' contains ' ' +]], +[[a a b +]], +[[script.4s:5: warning: m4@&t@_append_uniq: 'a b' contains ' ' ]]) AT_CLEANUP @@ -1328,9 +1315,8 @@ AT_KEYWORDS([m4@&t@_escape]) # commas and $ are not swallowed. This can easily happen because of # m4-listification. -AT_DATA_M4SUGAR([script.4s], -[[m4_init[]m4_divert([])dnl -m4_define([a], [OOPS])dnl +AT_CHECK_M4SUGAR_TEXT( +[[m4_define([a], [OOPS])dnl m4_escape([a[b $c#]d]) m4_if(m4_escape([a[b $c#]d]), [a[b $c#]d], [oops], m4_escape([a[b $c#]d]), [a@<:@b @S|@c@%:@@:>@d], [pass], [oops]) @@ -1348,9 +1334,7 @@ m4_text_wrap([Super long documentation.], [ ], [ --too-wide], 30) m4_text_wrap([First, second , third, [,quoted space]]) m4_define([xfff], [oops]) m4_text_wrap([Some $1 $2 $3 $4 embedded dollars.], [ $* ], [ $@ ], [0xfff & 20]) -]]) - -AT_DATA([expout], +]], [[a[b $c#]d pass @@ -1375,8 +1359,6 @@ First, second , third, [,quoted space] $* dollars. ]]) -AT_CHECK_M4SUGAR([-o-], 0, [expout]) - AT_CLEANUP ## -------------------- ## @@ -2001,115 +1983,73 @@ AT_CLEANUP ## Recursion. ## ## ----------- ## -AT_SETUP([recursion]) +# This script and its expected output are used in the next two tests. +m4_define([m4st_long_iteration_script], +[[m4_len(m4_foreach_w([j], m4_do(m4_for([i], [1], [10000], [], [,i ])), [j ])) +m4_shiftn(9998m4_for([i], [1], [10000], [], [,i])) +m4_len(m4_join([--],, m4_dquote_elt(m4_for([i], [1], [10000], [], [,i])),)) +m4_len(m4_joinall([--], m4_map([, m4_echo], + m4_dquote([1]m4_for([i], [2], [10000], [], [,i]))))) +m4_max(m4_min([1]m4_for([i], [2], [10000], [], + [,i]))m4_for([i], [2], [10000], [], [,i])) +m4_case([10000]m4_for([i], [1], [10000], [], [,i]),[end]) +m4_list_cmp(m4_dquote(1m4_for([i], [2], [10000], [], [,i])), + m4_dquote(m4_reverse(10000m4_for([i], [9999], [1], [], [,i])), [0])) +m4_list_cmp([0], [0m4_for([i], [1], [10000], [], [,0])]) +m4_list_cmp([0m4_for([i], [1], [10000], [], [,0])], [0]) +m4_for([i], [1], [10000], [], [m4_define(i)])dnl +m4_undefine(1m4_for([i], [2], [10000], [], [,i]))dnl +m4_bpatsubsts([a1]m4_for([i], [1], [10000], [], [,i]), [a2], [A]) +m4_bmatch([9997]m4_for([i], [1], [10000], [], [,^i$])) +m4_define([up], [m4_define([$1], m4_incr($1))$1])m4_define([j], 0)dnl +m4_cond(m4_for([i], [1], [10000], [], [[up([j])], [9990], i,]) [oops]) j +m4_count(m4_map_args_pair([,m4_quote], []m4_map_args([,m4_echo]m4_for([i], + [1], [10000], [], [,i])))) +]]) + +m4_define([m4st_long_iteration_output], +[[48894 +9999,10000 +78896 +58894 +10000 +end +0 +0 +0 +A +^9998$ +9990 9990 +5001 +]]) + +AT_SETUP([iteration macros, long lists (foreach/default)]) AT_KEYWORDS([m4@&t@_foreach m4@&t@_foreach_w m4@&t@_case m4@&t@_cond m4@&t@_bpatsubsts m4@&t@_shiftn m4@&t@_do m4@&t@_dquote_elt m4@&t@_reverse m4@&t@_map m4@&t@_join m4@&t@_joinall m4@&t@_list_cmp m4@&t@_max m4@&t@_min m4@&t@_bmatch m4@&t@_map_args m4@&t@_map_args_pair]) +dnl Test the foreach-based iteration macros unconditionally. +dnl Also test that the default version is efficient. +AT_CHECK_M4SUGAR_TEXT( +m4_defn([m4st_long_iteration_script]), +m4_defn([m4st_long_iteration_output]), +[], [], [foreach default]) + +AT_CLEANUP + +AT_SETUP([iteration macros, long lists (recursive)]) + dnl This test completes in a reasonable time if m4_foreach is linear, -dnl but thrashes if it is quadratic. If we are testing with m4 1.4.x, -dnl only the slower foreach.m4 implementation will work. But if we -dnl are testing with m4 1.6, we can rerun the test with __m4_version__ -dnl undefined to exercise the alternate code path. -AT_DATA_M4SUGAR([script.4s], -[[m4_init -m4_divert_push([])[]dnl -m4_len(m4_foreach_w([j], m4_do(m4_for([i], [1], [10000], [], [,i ])), [j ])) -m4_shiftn(9998m4_for([i], [1], [10000], [], [,i])) -m4_len(m4_join([--],, m4_dquote_elt(m4_for([i], [1], [10000], [], [,i])),)) -m4_len(m4_joinall([--], m4_map([, m4_echo], - m4_dquote([1]m4_for([i], [2], [10000], [], [,i]))))) -m4_max(m4_min([1]m4_for([i], [2], [10000], [], - [,i]))m4_for([i], [2], [10000], [], [,i])) -m4_case([10000]m4_for([i], [1], [10000], [], [,i]),[end]) -m4_list_cmp(m4_dquote(1m4_for([i], [2], [10000], [], [,i])), - m4_dquote(m4_reverse(10000m4_for([i], [9999], [1], [], [,i])), [0])) -m4_list_cmp([0], [0m4_for([i], [1], [10000], [], [,0])]) -m4_list_cmp([0m4_for([i], [1], [10000], [], [,0])], [0]) -m4_for([i], [1], [10000], [], [m4_define(i)])dnl -m4_undefine(1m4_for([i], [2], [10000], [], [,i]))dnl -m4_bpatsubsts([a1]m4_for([i], [1], [10000], [], [,i]), [a2], [A]) -m4_bmatch([9997]m4_for([i], [1], [10000], [], [,^i$])) -m4_define([up], [m4_define([$1], m4_incr($1))$1])m4_define([j], 0)dnl -m4_cond(m4_for([i], [1], [10000], [], [[up([j])], [9990], i,]) [oops]) j -m4_count(m4_map_args_pair([,m4_quote], []m4_map_args([,m4_echo]m4_for([i], - [1], [10000], [], [,i])))) -m4_divert_pop([]) -]]) +dnl but thrashes if it is quadratic. (2018 workstation: completes after +dnl five minutes.) Do not test the recursive version unless we have M4 1.6.x. +AT_SKIP_IF([$at_mfour_slow_recursion]) -AT_CHECK_M4SUGAR([-o-], [0], [[48894 -9999,10000 -78896 -58894 -10000 -end -0 -0 -0 -A -^9998$ -9990 9990 -5001 -]]) - -AT_DATA_M4SUGAR([script.4s], -[[m4_ifdef([__m4_version__], -[m4_undefine([__m4_version__])], -[m4_divert_push([])48894 -9999,10000 -78896 -58894 -10000 -end -0 -0 -0 -A -^9998$ -9990 9990 -5001 -m4_exit([0])]) -m4_init -m4_divert_push([])[]dnl -m4_len(m4_foreach_w([j], m4_do(m4_for([i], [1], [10000], [], [,i ])), [j ])) -m4_shiftn(9998m4_for([i], [1], [10000], [], [,i])) -m4_len(m4_join([--],, m4_dquote_elt(m4_for([i], [1], [10000], [], [,i])),)) -m4_len(m4_joinall([--], m4_map([, m4_echo], - m4_dquote([1]m4_for([i], [2], [10000], [], [,i]))))) -m4_max(m4_min([1]m4_for([i], [2], [10000], [], - [,i]))m4_for([i], [2], [10000], [], [,i])) -m4_case([10000]m4_for([i], [1], [10000], [], [,i]),[end]) -m4_list_cmp(m4_dquote(1m4_for([i], [2], [10000], [], [,i])), - m4_dquote(m4_reverse(10000m4_for([i], [9999], [1], [], [,i])), [0])) -m4_list_cmp([0], [0m4_for([i], [1], [10000], [], [,0])]) -m4_list_cmp([0m4_for([i], [1], [10000], [], [,0])], [0]) -m4_for([i], [1], [10000], [], [m4_define(i)])dnl -m4_undefine(1m4_for([i], [2], [10000], [], [,i]))dnl -m4_bpatsubsts([a1]m4_for([i], [1], [10000], [], [,i]), [a2], [A]) -m4_bmatch([9997]m4_for([i], [1], [10000], [], [,^i$])) -m4_define([up], [m4_define([$1], m4_incr($1))$1])m4_define([j], 0)dnl -m4_cond(m4_for([i], [1], [10000], [], [[up([j])], [9990], i,]) [oops]) j -m4_count(m4_map_args_pair([,m4_quote], []m4_map_args([,m4_echo]m4_for([i], - [1], [10000], [], [,i])))) -m4_divert_pop([]) -]]) - -AT_CHECK_M4SUGAR([-o-], [0], [[48894 -9999,10000 -78896 -58894 -10000 -end -0 -0 -0 -A -^9998$ -9990 9990 -5001 -]]) +AT_CHECK_M4SUGAR_TEXT( +m4_defn([m4st_long_iteration_script]), +m4_defn([m4st_long_iteration_output]), +[], [], [recursive]) AT_CLEANUP @@ -2237,30 +2177,6 @@ yes :,,::,a: ]]) -# Stress tests - check for linear scaling (won't necessarily fail if -# quadratic, but hopefully users will complain if it appears to hang) -AT_CHECK_M4SUGAR_TEXT([[dnl -m4_for([i], [1], [10000], [], [m4_set_add([a], i)])dnl -m4_set_add_all([b]m4_for([i], [1], [10000], [], [,i]))dnl -m4_set_remove([a], [1])dnl -m4_set_remove([b], [10000])dnl -m4_set_add_all([a]m4_for([i], [1], [10000], [], [,i]))dnl -m4_for([i], [1], [10000], [], [m4_set_add([b], i)])dnl -m4_len(m4_set_contents([a])) -m4_len(m4_set_foreach([b], [b], [m4_if(m4_eval(b & 1), [1], - [m4_set_remove([b], b, [-])])])) -m4_set_size([b]) -m4_define([prune3x], [m4_if(m4_eval([$1 % 3]), [0], - [m4_set_remove([a], [$1], [-])])])dnl -m4_len(m4_set_map([a], [prune3x])) -m4_count(m4_shift(m4_set_intersection([a], [b]))) -]], [[38894 -5000 -5000 -3333 -3334 -]]) - # Implementation corner cases. # m4_set_add_all dispatches to one of two different helper macros @@ -2316,6 +2232,69 @@ m4_set_dump([c], [,]) AT_CLEANUP +# This script and its expected output are used in the next two tests. +m4_define([m4st_set_stress_script], +[[dnl +m4_for([i], [1], [10000], [], [m4_set_add([a], i)])dnl +m4_set_add_all([b]m4_for([i], [1], [10000], [], [,i]))dnl +m4_set_remove([a], [1])dnl +m4_set_remove([b], [10000])dnl +m4_set_add_all([a]m4_for([i], [1], [10000], [], [,i]))dnl +m4_for([i], [1], [10000], [], [m4_set_add([b], i)])dnl +m4_len(m4_set_contents([a])) +m4_len(m4_set_foreach([b], [b], [m4_if(m4_eval(b & 1), [1], + [m4_set_remove([b], b, [-])])])) +m4_set_size([b]) +m4_define([prune3x], [m4_if(m4_eval([$1 % 3]), [0], + [m4_set_remove([a], [$1], [-])])])dnl +m4_len(m4_set_map([a], [prune3x])) +m4_count(m4_shift(m4_set_intersection([a], [b]))) +]]) + +m4_define([m4st_set_stress_output], +[[38894 +5000 +5000 +3333 +3334 +]]) + +AT_SETUP([m4@&t@_set stress test (foreach/default)]) + +AT_KEYWORDS([m4@&t@_set_add_all m4@&t@_set_remove m4@&t@_set_contents +m4@&t@_set_foreach m4@&t@_set_size m4@&t@_set_map +m4@&t@_set_intersection]) + +# Stress tests - check for linear scaling (won't necessarily fail if +# quadratic, but hopefully users will complain if it appears to hang) +# Test the foreach-based iteration macros unconditionally. +# Also test that the default version is efficient. + +AT_CHECK_M4SUGAR_TEXT( +m4_defn([m4st_set_stress_script]), +m4_defn([m4st_set_stress_output]), +[], [], [foreach default]) + +AT_CLEANUP + +AT_SETUP([m4@&t@_set stress test (recursive)]) + +AT_KEYWORDS([m4@&t@_set_add_all m4@&t@_set_remove m4@&t@_set_contents +m4@&t@_set_foreach m4@&t@_set_size m4@&t@_set_map +m4@&t@_set_intersection]) + +# This test completes in a reasonable time if m4_foreach is linear, +# but thrashes if it is quadratic. (2018 workstation: completes after +# one minute.) Do not test the recursive version unless we have M4 1.6.x. +AT_SKIP_IF([$at_mfour_slow_recursion]) + +AT_CHECK_M4SUGAR_TEXT( +m4_defn([m4st_set_stress_script]), +m4_defn([m4st_set_stress_output]), +[], [], [recursive]) + +AT_CLEANUP + ## ---------------------- ## ## __file__ and __line__. ## -- 2.44.2