Add mkpatsubst

Bug: 181797530
Test: rbcrun build/make/tests/run.rbc
Change-Id: Id15daaf6e3ed68982e50cdc710563095ffcf57ba
diff --git a/core/product_config.rbc b/core/product_config.rbc
index 355d41d..8e85c4b 100644
--- a/core/product_config.rbc
+++ b/core/product_config.rbc
@@ -433,6 +433,38 @@
     """Prints info."""
     print(message)
 
+
+def __mkpatsubst_word(parsed_pattern,parsed_subst, word):
+    (before, after) = parsed_pattern
+    if not word.startswith(before):
+        return word
+    if not word.endswith(after):
+        return word
+    if len(parsed_subst) < 2:
+        return parsed_subst[0]
+    return parsed_subst[0] + word[len(before):len(word) - len(after)] + parsed_subst[1]
+
+
+def _mkpatsubst(pattern, replacement, s):
+    """Emulates Make's patsubst.
+
+    Tokenizes `s` (unless it is already a list), and then performs a simple
+    wildcard substitution (in other words, `foo%bar` pattern is equivalent to
+    the regular expression `^foo(.*)bar$, and the first `%` in replacement is
+    $1 in regex terms). Escaping % is not supported
+    """
+    if pattern.find("\\") >= 0:
+        fail("'\\' in pattern is not allowed")
+    parsed_pattern = pattern.split("%", 1)
+    words = s if type(s) == "list" else _mkstrip(s).split(" ")
+    if len(parsed_pattern) == 1:
+        out_words = [ replacement if x == pattern else x for x in words]
+    else:
+        parsed_replacement = replacement.split("%", 1)
+        out_words = [__mkpatsubst_word(parsed_pattern, parsed_replacement, x) for x in words]
+    return out_words if type(s) == "list" else " ".join(out_words)
+
+
 def _mkstrip(s):
     """Emulates Make's strip.
 
@@ -507,6 +539,7 @@
     indirect = _indirect,
     mkinfo = _mkinfo,
     mkerror = _mkerror,
+    mkpatsubst = _mkpatsubst,
     mkwarning = _mkwarning,
     mkstrip = _mkstrip,
     mksubst = _mksubst,
diff --git a/tests/run.rbc b/tests/run.rbc
index c7eaf3b..4cda180 100644
--- a/tests/run.rbc
+++ b/tests/run.rbc
@@ -34,6 +34,16 @@
 assert_eq("b1 b2", rblf.mksubst("a", "b", "a1 a2"))
 assert_eq(["b1", "x2"], rblf.mksubst("a", "b", ["a1", "x2"]))
 
+assert_eq("ABcdYZ", rblf.mkpatsubst("ab%yz", "AB%YZ", "abcdyz"))
+assert_eq("bcz", rblf.mkpatsubst("a%z", "A%Z", "bcz"))
+assert_eq(["Ay", "Az"], rblf.mkpatsubst("a%", "A%", ["ay", "az"]))
+assert_eq("AcZ bcz", rblf.mkpatsubst("a%z", "A%Z", "acz  bcz"))
+assert_eq("Abcd", rblf.mkpatsubst("a%", "A%", "abcd"))
+assert_eq("abcZ", rblf.mkpatsubst("%z", "%Z", "abcz"))
+assert_eq("azx b", rblf.mkpatsubst("az", "AZ", "azx  b"))
+assert_eq(["azx", "b"], rblf.mkpatsubst("az", "AZ", ["azx", "b"]))
+assert_eq("ABC", rblf.mkpatsubst("abc", "ABC", "abc"))
+
 globals, config = rblf.product_configuration("test/device", init)
 assert_eq(
     {