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