autoconf-patches
[Top][All Lists]
Advanced

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

Re: m4sugar speedups


From: Eric Blake
Subject: Re: m4sugar speedups
Date: Sat, 13 Oct 2007 08:36:47 -0600
User-agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.6) Gecko/20070728 Thunderbird/2.0.0.6 Mnenhy/0.7.5.666

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

According to Eric Blake on 10/2/2007 7:07 AM:
> 
> And while I'm at it, the regex in m4_version_cmp can be avoided (since
> m4_version_unletter generates output that is all numeric, we don't have to
> worry about re-quoting active macros, so a translit is faster than
> m4_split).  This in turn speeds up AC_PREREQ, which is used rather frequently.

Well, maybe AC_PREREQ is not used _that_ frequently (only 19 times in
coreutils, and 5 times in autoconf itself).  But this patch:
 a) makes m4_version_unletter robust, fixing it so that
  m4_define([a],[oops])AC_PREREQ([2.61a])
no longer gives bogus results
 b) makes m4_version_unletter handle multi-letter versions, like 2.61aa
(let's hope we never make it there :)  or, as is more likely with other
packages, 1.2pre.
 c) greatly reduces the number of expansions and regex uses!
2.61       m4_version_prereq([2.60b])  476   14
2.61       m4_version_prereq([2.61])   385   12
pre-patch  m4_version_prereq([2.60b])  109    6
pre-patch  m4_version_prereq([2.61a])  153    6
pre-patch  m4_version_prereq([2.61b])  163    6
post-patch m4_version_prereq([2.60b])   35    2
post-patch m4_version_prereq([2.61a])   69    2
post-patch m4_version_prereq([2.61b])   11    2

Not to mention it uses a COOL m4 feature - m4_list_cmp now bases its
recursion decision based on a double argument list (the implementation is
roughly [macro1(args1)(args2)], where the expansion of macro1 produces the
name of macro2).

2007-10-13  Eric Blake  <address@hidden>

        Make AC_PREREQ faster and more robust.
        * lib/m4sugar/m4sugar.m4 (m4_ignore, m4_unquote): New macros.
        (m4_version_prereq): Inline constant expansions.
        (m4_list_cmp): Reduce number of expansions, by avoiding m4_case.
        Rewrite in terms of [] list, not () list.
        (_m4_list_cmp, _m4_version_unletter): New helper macros.
        (m4_version_unletter): Write wrapper around new implementation to
        preserve old semantics.
        (m4_version_compare): Pass correct type of list, and avoid
        overhead of flattening expressions too early.
        (m4_do): Move to be near other quoting macros.
        (m4_max, m4_min): Always result in decimal output.
        * doc/autoconf.texi (Looping constructs): Add m4_car, m4_cdr.
        Move m4_do...
        (Evaluation Macros): ...here.  Add m4_ignore, m4_unquote.
        (Text processing Macros): Move m4_version_compare...
        (Number processing Macros): ...to this new node; document m4_cmp,
        m4_list_cmp, m4_sign, m4_max, m4_min.
        * tests/m4sugar.at (m4@&address@hidden): Enhance test, to pick
        up on bugs fixed by this patch.
        * NEWS: Document new macros.

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

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

iD8DBQFHENf+84KuGfSFAYARAtViAKCDxS8NrKjKi6nD1v1uWed/VGp3BgCeMJN3
OwqQaDkfuXSL5QmMzTf/opg=
=GcLK
-----END PGP SIGNATURE-----
>From 6e1d6e2c5bea266157056e5fce5fe9113f14002e Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Sat, 13 Oct 2007 07:47:46 -0600
Subject: [PATCH] Make AC_PREREQ faster and more robust.

* lib/m4sugar/m4sugar.m4 (m4_ignore, m4_unquote): New macros.
(m4_version_prereq): Inline constant expansions.
(m4_list_cmp): Reduce number of expansions, by avoiding m4_case.
Rewrite in terms of [] list, not () list.
(_m4_list_cmp, _m4_version_unletter): New helper macros.
(m4_version_unletter): Write wrapper around new implementation to
preserve old semantics.
(m4_version_compare): Pass correct type of list, and avoid
overhead of flattening expressions too early.
(m4_do): Move to be near other quoting macros.
(m4_max, m4_min): Always result in decimal output.
* doc/autoconf.texi (Looping constructs): Add m4_car, m4_cdr.
Move m4_do...
(Evaluation Macros): ...here.  Add m4_ignore, m4_unquote.
(Text processing Macros): Move m4_version_compare...
(Number processing Macros): ...to this new node; document m4_cmp,
m4_list_cmp, m4_sign, m4_max, m4_min.
* tests/m4sugar.at (m4@&address@hidden): Enhance test, to pick
up on bugs fixed by this patch.
* NEWS: Document new macros.

Signed-off-by: Eric Blake <address@hidden>
---
 ChangeLog              |   24 +++++++
 NEWS                   |   19 +++--
 doc/autoconf.texi      |  179 +++++++++++++++++++++++++++++++++++++++++------
 lib/m4sugar/m4sugar.m4 |  160 +++++++++++++++++++++++++-----------------
 tests/m4sugar.at       |    9 +++
 5 files changed, 295 insertions(+), 96 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 7c3bb04..ac752fd 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,27 @@
+2007-10-13  Eric Blake  <address@hidden>
+
+       Make AC_PREREQ faster and more robust.
+       * lib/m4sugar/m4sugar.m4 (m4_ignore, m4_unquote): New macros.
+       (m4_version_prereq): Inline constant expansions.
+       (m4_list_cmp): Reduce number of expansions, by avoiding m4_case.
+       Rewrite in terms of [] list, not () list.
+       (_m4_list_cmp, _m4_version_unletter): New helper macros.
+       (m4_version_unletter): Write wrapper around new implementation to
+       preserve old semantics.
+       (m4_version_compare): Pass correct type of list, and avoid
+       overhead of flattening expressions too early.
+       (m4_do): Move to be near other quoting macros.
+       (m4_max, m4_min): Always result in decimal output.
+       * doc/autoconf.texi (Looping constructs): Add m4_car, m4_cdr.
+       Move m4_do...
+       (Evaluation Macros): ...here.  Add m4_ignore, m4_unquote.
+       (Text processing Macros): Move m4_version_compare...
+       (Number processing Macros): ...to this new node; document m4_cmp,
+       m4_list_cmp, m4_sign, m4_max, m4_min.
+       * tests/m4sugar.at (m4@&address@hidden): Enhance test, to pick
+       up on bugs fixed by this patch.
+       * NEWS: Document new macros.
+
 2007-10-12  Eric Blake  <address@hidden>
 
        * doc/autoconf.texi (Text processing Macros): Fix bad merge.
diff --git a/NEWS b/NEWS
index 00d3019..95c7e3b 100644
--- a/NEWS
+++ b/NEWS
@@ -78,18 +78,20 @@ GNU Autoconf NEWS - User visible changes.
    - The following macros were previously available as undocumented
      interfaces; the macros are now documented as stable interfaces.
 
-     __oline__  m4_assert  m4_bmatch  m4_bpatsubsts  m4_case
-     m4_default  m4_divert_once  m4_divert_pop  m4_divert_push
+     __oline__  m4_assert  m4_bmatch  m4_bpatsubsts  m4_car  m4_case
+     m4_cdr  m4_default  m4_divert_once  m4_divert_pop  m4_divert_push
      m4_divert_text  m4_do  m4_errprintn  m4_fatal  m4_flatten
      m4_ifndef  m4_ifset  m4_ifval  m4_ifvaln  m4_location
-     m4_n  m4_shiftn  m4_strip  m4_version_compare  m4_warn
+     m4_n  m4_shiftn  m4_strip  m4_warn
 
    - The following macros were previously available as undocumented
-     interfaces, but had bugs.  Packages that relied on the
-     undocumented and buggy behavior should analyze their code to make
-     sure it still works with the new documented behavior.
+     interfaces, but had bug fixes or semantic changes as part of this
+     release.  Packages that relied on the undocumented behavior
+     should be analyzed to make sure they will still work with the
+     new documented behavior.
 
-     m4_join  m4_text_box  m4_text_wrap
+     m4_cmp  m4_list_cmp  m4_join  m4_sign  m4_text_box  m4_text_wrap
+     m4_version_compare
 
    - Packages using the undocumented m4sugar macro m4_PACKAGE_VERSION
      should consider using the new AC_AUTOCONF_VERSION instead.
@@ -115,7 +117,8 @@ GNU Autoconf NEWS - User visible changes.
    be used to take action depending on whether anything was appended.
 
 ** The following m4sugar macros are new:
-   m4_cond  m4_newline  m4_shift2  m4_shift3
+   m4_cond  m4_ignore  m4_max  m4_min  m4_newline  m4_shift2
+   m4_shift3  m4_unquote
 
 ** Warnings are now generated by default when an installer invokes
    'configure' with an unknown --enable-* or --with-* option.
diff --git a/doc/autoconf.texi b/doc/autoconf.texi
index 26ca6fd..f63dfa2 100644
--- a/doc/autoconf.texi
+++ b/doc/autoconf.texi
@@ -455,6 +455,7 @@ Programming in M4sugar
 * Looping constructs::          Iteration in M4
 * Evaluation Macros::           More quotation and evaluation control
 * Text processing Macros::      String manipulation in M4
+* Number processing Macros::    Arithmetic computation in M4
 * Forbidden Patterns::          Catching unexpanded macros
 
 Writing Autoconf Macros
@@ -10087,6 +10088,7 @@ define your own macros into these namespaces.
 * Looping constructs::          Iteration in M4
 * Evaluation Macros::           More quotation and evaluation control
 * Text processing Macros::      String manipulation in M4
+* Number processing Macros::    Arithmetic computation in M4
 * Forbidden Patterns::          Catching unexpanded macros
 @end menu
 
@@ -10585,7 +10587,42 @@ Expand to @var{text}, and add a newline if @var{text} 
is not empty.
 @node Looping constructs
 @subsection Looping constructs
 
-The following macros implement loops in M4.
+The following macros are useful in implementing recursive algorithms in
+M4, including loop operations.  An M4 list is formed by quoting a list
+of quoted elements; generally the lists are comma-separated, although
address@hidden is whitespace-separated.  For example, the list
address@hidden, [b,c]]} contains two elements: @samp{[a]} and @samp{[b,c]}.
+It is common to see lists with unquoted elements when those elements are
+not likely to be macro names, as in @samp{[fputc_unlocked,
+fgetc_unlocked]}.
+
address@hidden m4_car (@var{list})
address@hidden
+Expands to the quoted first element of the comma-separated quoted
address@hidden  Often used with @code{m4_cdr} to recursively iterate
+through a list.  Generally, when using quoted lists of quoted elements,
address@hidden should be called without any extra quotes.
address@hidden defmac
+
address@hidden m4_cdr (@var{list})
address@hidden
+Expands to a quoted list of all but the first element of the
+comma-separated quoted @var{list}, or the empty string if @var{list} had
+only one element.  Generally, when using quoted lists of quoted
+elements, @code{m4_cdr} should be called without any extra quotes.
+
+For example, this is an implementation of @code{m4_map}; note how each
+iteration of the helper macro @code{_m4_map} checks for the end of
+recursion, then merely applies the first argument to the first element
+of the list, then recurses with the rest of the list.
address@hidden
+m4_define([m4_map], [m4_if([$2], [[]], [], [_$0($@@)])])dnl
+m4_define([_m4_map], [m4_ifval([$2],
+  [$1(m4_unquote(m4_car($2)))[]$0([$1], m4_cdr($2))])])dnl
+m4_map([ m4_eval], [[1],[1+1]])
address@hidden 1 2
address@hidden example
address@hidden defmac
 
 @defmac m4_for (@var{var}, @var{first}, @var{last}, @ovar{step}, @
   @var{expression})
@@ -10607,8 +10644,9 @@ outputs two lines:
 @example
 m4_foreach([myvar], [[foo], [bar, baz]],
            [echo myvar
-])
-
+])dnl
address@hidden foo
address@hidden bar, baz
 @end example
 @end defmac
 
@@ -10622,14 +10660,7 @@ 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.
-
address@hidden m4_do (@dots{})
address@hidden
-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.
address@hidden defmac
address@hidden TODO document m4_map, m4_map_sep
 
 @defmac m4_shiftn (@var{count}, @dots{})
 @defmacx m4_shift2 (@dots{})
@@ -10649,18 +10680,59 @@ for two and three shifts, respectively.
 @subsection Evaluation Macros
 
 The following macros give some control over the order of the evaluation
-by adding or removing levels of quotes.  They are meant for hard-core M4
-programmers.
+by adding or removing levels of quotes.
+
address@hidden m4_do (@var{arg1}, @dots{})
address@hidden
+This macro loops over its arguments and expands each @var{arg} in
+sequence.  Its main use is for readability; it allows the use of
+indentation and fewer @code{dnl} to result in the same expansion.
address@hidden defmac
 
 @defmac m4_dquote (@var{arg1}, @dots{})
 @msindex{dquote}
 Return the arguments as a quoted list of quoted arguments.
+Conveniently, if there is just one @var{arg}, this effectively adds a
+level of quoting.
 @end defmac
 
address@hidden m4_ignore (@dots{})
address@hidden
+This macro was introduced in Autoconf 2.62.  Expands to nothing,
+ignoring all of its arguments.  By itself, this isn't very useful.
+However, it can be used to conditionally ignore an arbitrary number of
+arguments, by making a decision about which macro name to apply to a
+list of arguments.
address@hidden
+dnl foo outputs a message only if [debug] is defined.
+m4_define([foo],
+[m4_ifdef([debug],[AC_MSG_NOTICE],[m4_ignore])([debug message])])
address@hidden example
+
+Note that for earlier versions of Autoconf, the macro @code{__gnu__} can
+serve the same purpose, although it is less readable.
address@hidden defmac
+
address@hidden m4_noquote is too dangerous to document - it invokes macros that
address@hidden probably rely on @samp{[]} nested quoting for proper operation.  
The
address@hidden user should generally prefer m4_unquote instead.
+
 @defmac m4_quote (@var{arg1}, @dots{})
 @msindex{quote}
 Return the arguments as a single entity, i.e., wrap them into a pair of
-quotes.
+quotes.  This effectively collapses multiple arguments into one,
+although it loses whitespace after unquoted commas in the process.
address@hidden defmac
+
address@hidden m4_unquote (@var{arg1}, @dots{})
address@hidden
+This macro was introduced in Autoconf 2.62.  Expand each argument,
+separated by commas.  For a single @var{arg}, this effectively removes a
+layer of quoting, and @code{m4_unquote(address@hidden)} is more efficient
+than the equivalent @code{m4_do(address@hidden)}.  For multiple arguments,
+this results in an unquoted list of expansions.  This is commonly used
+with @code{m4_split}, in order to convert a single quoted list into a
+series of quoted elements.
 @end defmac
 
 The following example aims at emphasizing the difference between (i), not
@@ -10695,7 +10767,7 @@ mkargs
 
 
 @node Text processing Macros
address@hidden Text processing Macros
address@hidden String manipulation in M4
 
 The following macros may be used to manipulate strings in M4.
 They are not intended for casual use.
@@ -10852,17 +10924,76 @@ Return @var{string} with letters converted to upper 
or lower case,
 respectively.
 @end defmac
 
address@hidden Number processing Macros
address@hidden Arithmetic computation in M4
+
+The following macros make it easier to deal with arithmetic operations.
+Where a parameter is documented as taking an arithmetic expression, you
+can use anything that can be parsed by @code{m4_eval}.
+
address@hidden m4_cmp (@var{expr-1}, @var{expr-2})
address@hidden
+Compare the arithmetic expressions @var{expr-1} and @var{expr-2}, and
+expand to @samp{-1} if @var{expr-1} is smaller, @samp{0} if they are
+equal, and @samp{1} if @var{expr-1} is larger.
address@hidden defmac
+
address@hidden m4_list_cmp (@var{list-1}, @var{list-2})
address@hidden
+Compare the two M4 lists consisting of comma-separated arithmetic
+expressions, left to right.  Expand to @samp{-1} for the first element
+pairing where the value from @var{list-1} is smaller, @samp{1} where the
+value from @var{list-2} is smaller, or @samp{0} if both lists have the
+same values.  If one list is shorter than the other, the remaining
+elements of the longer list are compared against 0.
address@hidden
+m4_list_cmp([1, 0],       [1])
address@hidden
+m4_list_cmp([1, [1 * 0]], [1, 0])
address@hidden
+m4_list_cmp([1, 2],       [1, 0])
address@hidden
+m4_list_cmp([1, [1+1], 3],[1, 2])
address@hidden
+m4_list_cmp([1, 2, -3],   [1, 2])
address@hidden
+m4_list_cmp([1, 0],       [1, 2])
address@hidden
+m4_list_cmp([1],          [1, 2])
address@hidden
address@hidden example
address@hidden defmac
+
address@hidden m4_max (@var{arg1}, @dots{})
address@hidden
+This macro was introduced in Autoconf 2.62.  Expand to the decimal value
+of the maximum arithmetic expression among all the arguments.
address@hidden defmac
+
address@hidden m4_min (@var{arg1}, @dots{})
address@hidden
+This macro was introduced in Autoconf 2.62.  Expand to the decimal value
+of the minimum arithmetic expression among all the arguments.
address@hidden defmac
+
address@hidden m4_sign (@var{expr})
address@hidden
+Expand to @samp{-1} if the arithmetic expression @var{expr} is negative,
address@hidden if it is positive, and @samp{0} if it is zero.
address@hidden defmac
+
 @anchor{m4_version_compare}
 @defmac m4_version_compare (@var{version-1}, @var{version-2})
 @msindex{version_compare}
-This macro was introduced in Autoconf 2.53.  Compare the version strings
address@hidden and @var{version-2}, and expand to @samp{-1} if
address@hidden is smaller, @samp{0} if they are the same, or @samp{1}
address@hidden is smaller.  Version strings must be a list of elements
-separated by @samp{.}, where each element is a number along with an
-optional lower case letter.  The comparison stops at the leftmost
-element that contains a difference, although a 0 element compares equal
-to a missing element.
+This macro was introduced in Autoconf 2.53, but had a number of
+usability limitations that were not lifted until Autoconf 2.62.  Compare
+the version strings @var{version-1} and @var{version-2}, and expand to
address@hidden if @var{version-1} is smaller, @samp{0} if they are the same,
+or @samp{1} @var{version-2} is smaller.  Version strings must be a list
+of elements separated by @samp{.}, where each element is a number along
+with optional case-insensitive letters designating beta releases.  The
+comparison stops at the leftmost element that contains a difference,
+although a 0 element compares equal to a missing element.
 
 @example
 m4_version_compare([1.1], [2.0])
@@ -10875,6 +11006,8 @@ m4_version_compare([1.2], [1.1.1a])
 @result{}1
 m4_version_compare([1.0], [1])
 @result{}0
+m4_version_compare([1.1pre], [1.1PRE])
address@hidden
 m4_version_compare([1.1a], [1,10])
 @result{}-1
 @end example
diff --git a/lib/m4sugar/m4sugar.m4 b/lib/m4sugar/m4sugar.m4
index 677695b..dd5138e 100644
--- a/lib/m4sugar/m4sugar.m4
+++ b/lib/m4sugar/m4sugar.m4
@@ -491,18 +491,6 @@ m4_define([_m4_bpatsubsts],
           m4_shift3($@))])])
 
 
-
-# m4_do(STRING, ...)
-# ------------------
-# This macro invokes all its arguments (in sequence, of course).  It is
-# useful for making your macros more structured and readable by dropping
-# unnecessary dnl's and have the macros indented properly.
-m4_define([m4_do],
-[m4_if($#, 0, [],
-       $#, 1, [$1],
-       [$1[]m4_do(m4_shift($@))])])
-
-
 # m4_define_default(MACRO, VALUE)
 # -------------------------------
 # If MACRO is undefined, set it to VALUE.
@@ -610,16 +598,15 @@ m4_define([m4_undefine],
 ## 7. Quoting manipulation.  ##
 ## ------------------------- ##
 
-# m4_quote(ARGS)
-# --------------
-# Return ARGS as a single argument.  Any whitespace after unquoted commas
-# is stripped.
-#
-# It is important to realize the difference between `m4_quote(exp)' and
-# `[exp]': in the first case you obtain the quoted *result* of the
-# expansion of EXP, while in the latter you just obtain the string
-# `exp'.
-m4_define([m4_quote],  [[$*]])
+# m4_do(STRING, ...)
+# ------------------
+# This macro invokes all its arguments (in sequence, of course).  It is
+# useful for making your macros more structured and readable by dropping
+# unnecessary dnl's and have the macros indented properly.
+m4_define([m4_do],
+[m4_if([$#], 0, [],
+       [$#], 1, [$1],
+       [$1[]m4_do(m4_shift($@))])])
 
 
 # m4_dquote(ARGS)
@@ -628,16 +615,47 @@ m4_define([m4_quote],  [[$*]])
 m4_define([m4_dquote],  address@hidden)
 
 
+# m4_ignore(ARGS)
+# ---------------
+# Expands to nothing.  Useful for conditionally ignoring an arbitrary
+# number of arguments (see _m4_list_cmp for an example).
+m4_define([m4_ignore])
+
+
 # m4_noquote(STRING)
 # ------------------
 # Return the result of ignoring all quotes in STRING and invoking the
 # macros it contains.  Amongst other things, this is useful for enabling
 # macro invocations inside strings with [] blocks (for instance regexps
-# and help-strings).
+# and help-strings).  On the other hand, since all quotes are disabled,
+# any macro expanded during this time that relies on nested [] quoting
+# will likely crash and burn.  This macro is seldom useful; consider
+# m4_unquote instead.
 m4_define([m4_noquote],
 [m4_changequote(-=<{,}>=-)$1-=<{}>=-m4_changequote([,])])
 
 
+# m4_quote(ARGS)
+# --------------
+# Return ARGS as a single argument.  Any whitespace after unquoted commas
+# is stripped.
+#
+# It is important to realize the difference between `m4_quote(exp)' and
+# `[exp]': in the first case you obtain the quoted *result* of the
+# expansion of EXP, while in the latter you just obtain the string
+# `exp'.
+m4_define([m4_quote],  [[$*]])
+
+
+# m4_unquote(ARGS)
+# ----------------
+# Remove one layer of quotes from each ARG, performing one level of
+# expansion.  For one argument, m4_unquote([arg]) is more efficient than
+# m4_do([arg]), but for multiple arguments, the difference is that
+# m4_unquote separates arguments with commas while m4_do concatenates.
+m4_define([m4_unquote], [$*])
+
+
 ## -------------------------- ##
 ## 8. Implementing m4 loops.  ##
 ## -------------------------- ##
@@ -1850,42 +1868,50 @@ m4_define([m4_cmp],
 # m4_list_cmp(A, B)
 # -----------------
 #
-# Compare the two lists of integers A and B.  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
-#   m4_list_cmp((1, 2, 3),  (1, 2)) ->  1
-#   m4_list_cmp((1, 2, -3), (1, 2)) -> -1
-#   m4_list_cmp((1, 0),     (1, 2)) -> -1
-#   m4_list_cmp((1),        (1, 2)) -> -1
+# Compare the two lists of integer expressions A and B.  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
+#   m4_list_cmp([1, 2, 3],  [1, 2]) ->  1
+#   m4_list_cmp([1, 2, -3], [1, 2]) -> -1
+#   m4_list_cmp([1, 0],     [1, 2]) -> -1
+#   m4_list_cmp([1],        [1, 2]) -> -1
+#   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 to determine when to recurse.
 m4_define([m4_list_cmp],
-[m4_if([$1$2], [()()], 0,
-       [$1], [()], [$0((0), [$2])],
-       [$2], [()], [$0([$1], (0))],
-       [m4_case(m4_cmp(m4_car$1, m4_car$2),
-               -1, -1,
-                1, 1,
-                0, [$0((m4_shift$1), (m4_shift$2))])])])
-
-# m4_max(A, B, ...)
-# m4_min(A, B, ...)
+[m4_if([$1$2], [], 0,
+       [$1], [], [$0(0, [$2])],
+       [$2], [], [$0([$1], 0)],
+       [$1], [$2], 0,
+       [_$0(m4_cmp(m4_car($1), m4_car($2)))([$0(m4_cdr($1), m4_cdr($2))])])])
+m4_define([_m4_list_cmp],
+[m4_if([$1], 0, [m4_unquote], [$1m4_ignore])])
+
+# m4_max(EXPR, ...)
+# m4_min(EXPR, ...)
 # -----------------
-# Return the maximum (or minimum) of a series of integer expressions.
+# 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).
 m4_define([m4_max],
 [m4_if([$#], [0], [m4_fatal([too few arguments to $0])],
-       [$#], [1], [$1],
-       [$#$1], [2$2], [$1],
+       [$#], [1], [m4_eval([$1])],
+       [$#$1], [2$2], [m4_eval([$1])],
        [$#], [2],
        [m4_eval((([$1]) > ([$2])) * ([$1]) + (([$1]) <= ([$2])) * ([$2]))],
        [$0($0([$1], [$2]), m4_shift2($@))])])
 m4_define([m4_min],
 [m4_if([$#], [0], [m4_fatal([too few arguments to $0])],
-       [$#], [1], [$1],
-       [$#$1], [2$2], [$1],
+       [$#], [1], [m4_eval([$1])],
+       [$#$1], [2$2], [m4_eval([$1])],
        [$#], [2],
        [m4_eval((([$1]) < ([$2])) * ([$1]) + (([$1]) >= ([$2])) * ([$2]))],
        [$0($0([$1], [$2]), m4_shift2($@))])])
@@ -1906,23 +1932,28 @@ m4_define([m4_sign],
 
 # m4_version_unletter(VERSION)
 # ----------------------------
-# Normalize beta version numbers with letters to numbers only for comparison.
+# Normalize beta version numbers with letters to numeric expressions, which
+# can then be handed to m4_eval for the purpose of comparison.
 #
 #   Nl -> (N+1).-1.(l#)
 #
-#i.e., 2.14a -> 2.15.-1.1, 2.14b -> 2.15.-1.2, etc.
-# This macro is absolutely not robust to active macro, it expects
-# reasonable version numbers and is valid up to `z', no double letters.
+# for example:
+#   [2.14a] -> [2.14+1.-1.[0r36:a]] -> 2.15.-1.10
+#   [2.14b] -> [2.15+1.-1.[0r36:b]] -> 2.15.-1.11
+#   [2.61aa.b] -> [2.61+1.-1.[0r36:aa],+1.-1.[0r36:b]] -> 2.62.-1.370.1.-1.11
+#
+# This macro expects reasonable version numbers, but can handle double
+# letters and does not expand one-letter macros.  Inline constant expansions,
+# to avoid m4_defn overhead.  _m4_version_unletter is the real workhorse
+# used by m4_version_compare, but since [0r36:a] is less readable than 10,
+# we provide a wrapper for human use.
 m4_define([m4_version_unletter],
-[m4_translit(m4_bpatsubsts(m4_tolower([[$1]]),
-                          [\([0-9]+\)\([abcdefghi]\)],
-                            [m4_eval(\1 + 1).-1.\2],
-                          [\([0-9]+\)\([jklmnopqrs]\)],
-                            [m4_eval(\1 + 1).-1.1\2],
-                          [\([0-9]+\)\([tuvwxyz]\)],
-                            [m4_eval(\1 + 1).-1.2\2]),
-            [abcdefghijklmnopqrstuvwxyz],
-            [12345678901234567890123456])])
+[m4_map_sep([m4_eval], [.], _$0([$1]))])
+m4_define([_m4_version_unletter],
+[m4_translit(m4_bpatsubst([[[$1]]], ]dnl
+m4_dquote(m4_dquote(m4_defn([m4_cr_Letters])))[[+],
+                         [+1.-1.[0r36:\&]]),
+            [.], [,])])
 
 
 # m4_version_compare(VERSION-1, VERSION-2)
@@ -1932,8 +1963,7 @@ m4_define([m4_version_unletter],
 #   0 if           =
 #   1 if           >
 m4_define([m4_version_compare],
-[m4_list_cmp((m4_translit(m4_version_unletter([$1]), [.], [,])),
-            (m4_translit(m4_version_unletter([$2]), [.], [,])))])
+[m4_list_cmp(_m4_version_unletter([$1]), _m4_version_unletter([$2]))])
 
 
 # m4_PACKAGE_NAME
@@ -1949,12 +1979,12 @@ m4_include([m4sugar/version.m4])
 # ----------------------------------------------------
 # Check this Autoconf version against VERSION.
 m4_define([m4_version_prereq],
-[m4_if(m4_version_compare(m4_defn([m4_PACKAGE_VERSION]), [$1]), -1,
+[m4_if(m4_version_compare(]m4_dquote(m4_defn([m4_PACKAGE_VERSION]))[, [$1]),
+       [-1],
        [m4_default([$3],
                   [m4_fatal([Autoconf version $1 or higher is required],
-                            63)])],
-       [$2])[]dnl
-])
+                            [63])])],
+       [$2])])
 
 
 
diff --git a/tests/m4sugar.at b/tests/m4sugar.at
index 3c537e0..a946104 100644
--- a/tests/m4sugar.at
+++ b/tests/m4sugar.at
@@ -313,6 +313,11 @@ m4_version_compare([1.2], [1.1.1a])
 m4_version_compare([1.0], [1])
 m4_version_compare([1.0a], [1.0a])
 m4_version_compare([1.1a], [1.1a.1])
+m4_version_compare([1.10], [1.1a])
+m4_version_compare([1.1a], [1.1A])
+m4_define([a], [oops])dnl
+m4_version_compare([1.1a], [1.1A])
+m4_version_compare([1z], [1aa])
 ]],
 [[-1
 1
@@ -322,6 +327,10 @@ m4_version_compare([1.1a], [1.1a.1])
 0
 0
 -1
+1
+0
+0
+-1
 ]])
 
 AT_CLEANUP
-- 
1.5.3.2


reply via email to

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