New macros for M4sugar

Stefano Gioffré <[email protected]>
Newsgroups gmane.comp.sysutils.autoconf.patches
Message-ID <CAExyPMc3kr6V4vXZoHqEzRo29yUKShU-R8W0bPkqvE3DBghvfQ@mail.gmail.com>
Hi!

This is just an open proposal. I have imported some self-documented macros
of mine to autoconf/lib/m4sugar/m4sugar.m4 (please see the patch attached):

   - m4_case_in(): Searches for the existence of a text within one or more
   lists, using a syntax identical to that of m4_case()
   - m4_define_substrings_as(): Searches for the first match of a regular
   expression in a string and defines custom macros accordingly
   - m4_for_each_match(): Calls a custom macro for every occurrence of a
   regular expression in a string
   - m4_get_replacements() Replaces every occurrence of a regular
   expression in a string with the text returned by a custom macro, invoked
   for each match
   - m4_lambda(): Creates an anonymous macro on the fly, able to be passed
   as a callback argument
   - m4_list_index(): Searches for the first occurence of a string in a
   list and returns its position, or -1 if the string as not been found
   - m4_repeat(): Repeats a text *n* times
   - m4_redepth(): Examines a regular expression and returns the number of
   capturing parentheses present

Please browse the patched m4sugar.m4 file for the complete documentation
with examples. This patch refers to commit
5b9db67786a428164abafe626ab11a2754aad528 (2019-09-10 08:00:33 -0700).

All the best

--madmurphy
autoconf.patch (text/x-patch, 12.5 KB)
diff --git a/lib/m4sugar/m4sugar.m4 b/lib/m4sugar/m4sugar.m4
index bbd69583..c121bb85 100644
--- a/lib/m4sugar/m4sugar.m4
+++ b/lib/m4sugar/m4sugar.m4
@@ -407,6 +407,38 @@ m4_define([m4_case],
        [$0([$1], m4_shift3($@))])])
 
 
+# m4_case_in(TEXT, LIST1, IF-FOUND1[, ... LISTN, IF-FOUNDN], [IF-NOT-FOUND])
+# --------------------------------------------------------------------------
+# Searches for the first occurence of `TEXT` in each comma-separated list
+# `LISTN`
+#
+# For example,
+#
+#     m4_case_in([charlie],
+#         [[rose], [madmurphy], [lili], [frank]],
+#             [Official release],
+#         [[rick], [karl], [matilde]],
+#             [Semi-official release],
+#         [[jack], [charlie]],
+#             [Offensive release],
+#             [Unofficial release])
+#
+# `m4_case_in()` has been designed to behave like `m4_case()` when a simple
+# string is passed instead of a list.
+#
+# Author: madmurphy (https://github.com/madmurphy/not-autotools)
+m4_define([m4_case_in],
+	[m4_cond([m4_eval($# < 2)], [1],
+			[],
+		[m4_argn(1, $2)], [$1],
+			[$3],
+		[m4_eval(m4_count($2) > 1)], [1],
+			[m4_case_in([$1], m4_dquote(m4_shift($2)), m4_shift2($@))],
+		[m4_eval($# > 4)], [1],
+			[m4_case_in([$1], m4_shift3($@))],
+			[$4])])
+
+
 # m4_bmatch(SWITCH, RE1, VAL1, RE2, VAL2, ..., DEFAULT)
 # -----------------------------------------------------
 # m4 equivalent of
@@ -626,6 +658,40 @@ m4_define([m4_default_nblank_quoted],
 [m4_ifblank([$1], [[$2]], [[$1]])])
 
 
+# m4_define_substrings_as(STRING, REGEXP, MACRO0[, MACRO1[, ... MACRON ]])
+# ------------------------------------------------------------------------
+# Searches for the first match of `REGEXP` in `STRING` and defines custom
+# macros accordingly
+#
+# For both the entire regular expression `REGEXP` (`\0`) and each
+# sub-expression within capturing parentheses (`\1`, `\2`, `\3`, ... , `\N`)
+# a macro expanding to the corresponding matching text will be created, named
+# according to the argument `MACRON` passed. If a `MACRON` argument is
+# omitted or empty, the corresponding parentheses in the regular expression
+# will be considered as non-capturing. If `REGEXP` cannot be found in
+# `STRING` no macro will be defined. If `REGEXP` can be found but some of its
+# capturing parentheses cannot, the macro(s) corresponding to the latter will
+# be defined as empty strings.
+#
+# Example -- Get the current version string from a file named `VERSION`:
+#
+#     m4_define_substrings_as(
+#         m4_quote(m4_include([VERSION])),
+#         [\([0-9]+\)\.\([0-9]+\)\.\([0-9]+\)],
+#         [VERSION_STR], [VERSION_MAJ], [VERSION_MIN], [VERSION_REV]      
+#     )
+#     AC_INIT([foo], VERSION_MAJ[.]VERSION_MIN[.]VERSION_REV)
+#
+# Author: madmurphy (https://github.com/madmurphy/not-autotools)
+m4_define([m4_define_substrings_as],
+	[m4_bregexp([$1], [$2],
+		m4_if([$3], [], [],
+			[[m4_define([$3], [m4_quote(\&)])]])m4_if(m4_eval($# > 3), [1],
+			[m4_for([_idx_], [1], [$# - 3], [1],
+				[m4_if(m4_normalize(m4_argn(_idx_, m4_shift3($@))), [], [],
+					[[m4_define(m4_quote(m4_normalize(m4_argn(]_idx_[, m4_shift3($@)))), m4_quote(\]_idx_[))]])])]))])
+
+
 # m4_defn(NAME)
 # -------------
 # Like the original, except guarantee a warning when using something which is
@@ -710,6 +776,25 @@ m4_define([m4_popdef],
        [m4_map_args([$0], $@)])])
 
 
+# m4_redepth(REGEXP)
+# ------------------
+# Examines a regular expression and returns the number of capturing
+# parentheses present
+#
+# The returned number is the highest available sub-match that can be written
+# as `\[number]` during a replacement
+#
+# For example,
+#
+#     m4_redepth([\([0-9]+\)\.\([0-9]+\)\.\([0-9]+\)])
+#
+# expands to `3`.
+#
+# Author: madmurphy (https://github.com/madmurphy/not-autotools)
+m4_define([m4_redepth],
+	[m4_len(m4_bpatsubst(m4_bpatsubst([$1], [\(\\\|)\|\([^\\]\|^\)\(\\\\\)*(\)\|\(\\\)(\|,], [\4]), [[^\\]], []))])
+
+
 # m4_shiftn(N, ...)
 # -----------------
 # Returns ... shifted N times.  Useful for recursive "varargs" constructs.
@@ -1186,6 +1271,39 @@ m4_define([m4_foreach_w],
   [m4_define([$1],], [)$3])m4_popdef([$1])])
 
 
+# m4_list_index(LIST, TARGET, [ADD-TO-RETURN-VALUE], [IF-NOT-FOUND])
+# ------------------------------------------------------------------
+# Searches for the first occurence of `TARGET` in the comma-separated list
+# `LIST` and returns its position, or `-1` if `TARGET` has not been found
+#
+# For example,
+#
+#     m4_list_index([[foo], [bar], [hello]], [bar])
+#
+# expands to `2`.
+#
+# If the `ADD-TO-RETURN-VALUE` argument is expressed (this accepts only
+# numbers, both positive and negative), it will be added to the returned
+# index -- if `TARGET` has not been found and the `IF-NOT-FOUND` argument is
+# omitted, it will be added to `-1`.
+#
+# If the `IF-NOT-FOUND` argument is expressed, it will be returned every time
+# `TARGET` is not found. This argument accepts both numerical and
+# non-numerical values.
+#
+# Author: madmurphy (https://github.com/madmurphy/not-autotools)
+m4_define([m4_list_index],
+	[m4_cond([m4_eval($# < 2)], [1],
+			[-1],
+		[m4_argn(1, $1)], [$2],
+			[m4_eval([$3] + 0)],
+		[m4_eval(m4_count($1) > 1)], [1],
+			[m4_list_index(m4_dquote(m4_shift($1)), [$2], m4_eval([$3] + 1), m4_if(m4_eval($# > 3), [1], [$4], [m4_eval([$3] - 1)]))],
+		[m4_eval($# > 3)], [1],
+			[$4],
+			[m4_eval([$3] - 1)])])
+
+
 # m4_map(MACRO, LIST)
 # m4_mapall(MACRO, LIST)
 # ----------------------
@@ -1305,6 +1423,61 @@ m4_define([_m4_map_args_w],
 [m4_substr([$1], [$2], m4_eval(m4_len([$1]) - [$2] - [$3]))])
 
 
+
+
+# m4_repeat(N_TIMES, TEXT)
+# ------------------------
+# Repeats `TEXT` `N_TIMES`
+#
+# Every occurrence of `$#` within `TEXT` will be replaced with the current
+# index. For example,
+#
+#     m4_repeat([4], [foo $#...])
+#
+# will expand to
+#
+#     foo 1...foo 2...foo 3...foo 4...
+#
+# If `m4_repeat()` is invoked from within a macro body, `$#` will be replaced
+# with higher priority with the current macro's number of arguments. For
+# instance,
+#
+#     m4_define([print_foo], [m4_repeat([4], [foo $#...])])
+#     print_foo
+#
+# will expand to
+#
+#     foo 0...foo 0...foo 0...foo 0...
+#
+# Therefore, in order to inhibit the immediate expansion of `$#` it is
+# necessary temporarily to break its components, as in the following example,
+#
+#     m4_define([print_foo], [m4_repeat([4], [foo ][$][#][...])])
+#     print_foo
+#
+# which will finally expand to
+#
+#     foo 1...foo 2...foo 3...foo 4...
+#
+# This applies also to macro calls. For example,
+#
+#     m4_define([even_numbers],
+#         [(0)m4_repeat([$1], [, m4_eval(][$][#][ * 2)])])
+#
+#     Even numbers: even_numbers([10]) ...
+#
+# will expand to
+#
+#     Even numbers: (0), 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 ...
+#
+# However, for complex cases it is suggested to use `m4_for()`.
+#
+# Author: madmurphy (https://github.com/madmurphy/not-autotools)
+m4_define([m4_repeat],
+	[m4_if(m4_eval([$1] > 0), [1],
+		[m4_repeat(m4_decr([$1]), [$2])[]m4_bpatsubst([$2], [\$][#], [$1])])])
+
+
 # m4_stack_foreach(MACRO, FUNC)
 # m4_stack_foreach_lifo(MACRO, FUNC)
 # ----------------------------------
@@ -1998,6 +2171,96 @@ m4_define([_m4_defun_once],
 [m4_pushdef([$1])$3[$2[]m4_provide([$1])]$4])
 
 
+# m4_lambda(MACRO-BODY)
+# ---------------------
+# Creates an anonymous macro on the fly, able to be passed as a callback
+# argument
+#
+# For example, in the following code a lambda macro instead of a named one is
+# passed to `m4_map()`:
+#
+#     m4_define([MISSING_PROGRAMS], [[find], [xargs], [sed]])
+#     Install first m4_map([m4_lambda(["$1", ])], [MISSING_PROGRAMS])then proceed.
+#
+# The code above will print:
+#
+#     Install first "find", "xargs", "sed", then proceed.
+#
+# By using the `m4_anon` keyword, a lambda macro can invoke itself repeatedly
+# (recursion). For example,
+#
+#     m4_lambda([{$1}m4_if(m4_eval($# > 1), [1], [m4_anon(m4_shift($*))])])([one], [two], [three], [four])
+#
+# will print
+#
+#     {one}{two}{three}{four}
+#
+# Alternatively you can use the `$0` shortcut, which expands to `m4_anon`:
+#
+#     m4_lambda([{$1}m4_if(m4_eval($# > 1), [1], [$0(m4_shift($*))])])([one], [two], [three], [four])
+#
+# The `m4_anon` keyword is available only from within the lambda macro body
+# and works in a stack-like fashion. Do not attempt to redefine it yourself.
+#
+# Lambda macros can be nested within each other:
+#
+#     m4_lambda([Hi there! m4_lambda([This is a nested lambda macro!])])
+#
+# However, as with any other type of macro, reading the arguments of a nested
+# lambda macro might be difficult. Consider for example the following code
+# snippet:
+#
+#     m4_lambda([Hi there! Here it's $1! m4_lambda([And here it's $1!])([Charlie])])([Rose])
+#
+# It will print:
+#
+#     Hi there! Here it's Rose! And here it's Rose!
+#
+# This is because `$1` gets replaced with `Rose` before the nested macro's
+# arguments can expand. The only way to prevent this is to delay the
+# composition of `$` and `1`, so that the expansion of the argument happens
+# at a later time. Hence,
+#
+#     m4_lambda([Hi there! Here it's $1! m4_lambda([And here it's ][$][1][!])([Charlie])])([Rose])
+#
+# will finally print:
+#
+#     Hi there! Here it's Rose! And here it's Charlie!
+#
+# This applies also to other argument notations, such as `$#`, `$*` and `#@`.
+#
+# There is no particular limit in the level of nesting reachable, except good
+# coding practices. As an extreme example, consider the following snippet,
+# consisting in three lambda macros nested within each other, whose innermost
+# one is also recursive (the atypical M4 indentation is only for clarity):
+#
+#     # Let's use `L()` as a shortcut for `m4_lambda()`...
+#     m4_define([L], m4_defn([m4_lambda]))
+#
+#     L([
+#         This is $1 L([
+#             This is ][$][1][ L([
+#                 {][$][1][}m4_if(m4_eval(][$][#][ > 1), [1],
+#                     [m4_anon(m4_shift(]m4_quote(][$][*][)[))])
+#                 ])([internal-1], [internal-2], [internal-3], [internal-4])
+#             ])([central])
+#     ])([external])
+#
+# The example above will print something like this (plus some trailing spaces
+# due to the bad indentation):
+#
+#     This is external 
+#         This is central 
+#             {internal-1}
+#             {internal-2}
+#             {internal-3}
+#             {internal-4}
+#
+# Author: madmurphy (https://github.com/madmurphy/not-autotools)
+m4_define([m4_lambda],
+	[m4_pushdef([m4_anon], [$1])[]m4_pushdef([m4_anon], [m4_popdef([m4_anon])[]$1[]m4_popdef([m4_anon])])[]m4_anon])
+
+
 # m4_pattern_forbid(ERE, [WHY])
 # -----------------------------
 # Declare that no token matching the forbidden perl extended regular
@@ -2219,6 +2482,52 @@ m4_define([_m4_define_cr_not],
 	   m4_translit(m4_dquote(m4_defn([m4_cr_all])),
 		       m4_defn([m4_cr_$1])))])
 
+# m4_for_each_match(STRING, REGEXP, MACRO)
+# ----------------------------------------
+# Calls the custom macro `MACRO` for every occurrence of `REGEXP` in `STRING`
+#
+# The text that matches the entire `REGEXP` and all the sub-strings that
+# match its capturing parentheses will be passed to `MACRO` as arguments.
+#
+# For example,
+#
+#     m4_define([custom_macro], [...foo $1|$2|$3|$4 bar])
+#     m4_for_each_match([blaablabblac], [\(b\(l\)\)\(a\)], [custom_macro])
+#
+# will print:
+#
+#     ...foo bla|bl|l|a bar...foo bla|bl|l|a bar...foo bla|bl|l|a bar
+#
+# Requires: `m4_redepth()`
+# Author: madmurphy (https://github.com/madmurphy/not-autotools)
+m4_define([m4_for_each_match],
+	[m4_if(m4_bregexp([$1], [$2]), [-1], [],
+		[m4_bregexp([$1], [$2], [[]$3([\&]]m4_quote(m4_for([_idx_], [1], m4_quote(m4_redepth([$2])), [1], [, \_idx_]))[)])[]m4_for_each_match(m4_substr([$1], m4_eval(m4_bregexp([$1], [$2]) + m4_len(m4_bregexp([$1], [$2], [\&])))), [$2], [$3])])])
+
+
+# m4_get_replacements(STRING, REGEXP, MACRO)
+# ------------------------------------------
+# Replaces every occurrence of `REGEXP` in `STRING` with the text returned by
+# the custom macro `MACRO`, invoked for each match
+#
+# The text that matches the entire `REGEXP` and all the sub-strings that
+# match its capturing parentheses will be passed to `MACRO` as arguments.
+#
+# For example,
+#
+#     m4_define([custom_macro], [XX$3])
+#     m4_get_replacements([hello you world!!], [\(l\|w\)+\(o\)], [custom_macro])
+#
+# will print:
+#
+#     heXXo you XXorld!!
+#
+# Requires: `m4_redepth()`
+# Author: madmurphy (https://github.com/madmurphy/not-autotools)
+m4_define([m4_get_replacements],
+	[m4_if(m4_bregexp([$1], [$2]), [-1], [$1],
+		[m4_bpatsubst([$1], [$2], [[]$3([\&]]m4_quote(m4_for([_idx_], [1], m4_quote(m4_redepth([$2])), [1], [, \_idx_]))[)])])])
+
 
 # m4_cr_not_letters
 # m4_cr_not_LETTERS
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.