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
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

Reply via email to