Add string_replace builtin function
This change introduces a string_replace builtin function that can
be used to replace occurrences of a string within a string with
another string.
Change-Id: I7b884863c6bc28cd02157fdd51052224399a195d
Reviewed-on: https://gn-review.googlesource.com/2560
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Petr Hosek <phosek@google.com>
diff --git a/docs/reference.md b/docs/reference.md
index 48017d5..f2b84f9 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -52,6 +52,7 @@
* [set_defaults: Set default values for a target type.](#set_defaults)
* [set_sources_assignment_filter: Set a pattern to filter source files.](#set_sources_assignment_filter)
* [split_list: Splits a list into N different sub-lists.](#split_list)
+ * [string_replace: Replaces substring in the given string.](#string_replace)
* [template: Define a template rule.](#template)
* [tool: Specify arguments to a toolchain tool.](#tool)
* [toolchain: Defines a toolchain.](#toolchain)
@@ -2605,6 +2606,27 @@
Will print:
[[1, 2], [3, 4], [5, 6]
```
+### <a name="string_replace"></a>**string_replace**: Replaces substring in the given string.
+
+```
+ result = string_replace(str, old, new[, max])
+
+ Returns a copy of the string str in which the occurrences of old have been
+ replaced with new, optionally restricting the number of replacements. The
+ replacement is performed sequentially, so if new contains old, it won't be
+ replaced.
+```
+
+#### **Example**
+
+```
+ The code:
+ mystr = "Hello, world!"
+ print(string_replace(mystr, "world", "GN"))
+
+ Will print:
+ Hello, GN!
+```
### <a name="template"></a>**template**: Define a template rule.
```
diff --git a/tools/gn/functions.cc b/tools/gn/functions.cc
index b5bb1e3..7fc8b62 100644
--- a/tools/gn/functions.cc
+++ b/tools/gn/functions.cc
@@ -7,6 +7,7 @@
#include <stddef.h>
#include <iostream>
#include <memory>
+#include <regex>
#include <utility>
#include "base/environment.h"
@@ -1079,6 +1080,75 @@
return result;
}
+// string_replace --------------------------------------------------------------
+
+const char kStringReplace[] = "string_replace";
+const char kStringReplace_HelpShort[] =
+ "string_replace: Replaces substring in the given string.";
+const char kStringReplace_Help[] =
+ R"(string_replace: Replaces substring in the given string.
+
+ result = string_replace(str, old, new[, max])
+
+ Returns a copy of the string str in which the occurrences of old have been
+ replaced with new, optionally restricting the number of replacements. The
+ replacement is performed sequentially, so if new contains old, it won't be
+ replaced.
+
+Example
+
+ The code:
+ mystr = "Hello, world!"
+ print(string_replace(mystr, "world", "GN"))
+
+ Will print:
+ Hello, GN!
+)";
+
+Value RunStringReplace(Scope* scope,
+ const FunctionCallNode* function,
+ const std::vector<Value>& args,
+ Err* err) {
+ if (args.size() < 3 || args.size() > 4) {
+ *err = Err(function, "Wrong number of arguments to string_replace().");
+ return Value();
+ }
+
+ if (!args[0].VerifyTypeIs(Value::STRING, err))
+ return Value();
+ const std::string str = args[0].string_value();
+
+ if (!args[1].VerifyTypeIs(Value::STRING, err))
+ return Value();
+ const std::string& old = args[1].string_value();
+
+ if (!args[2].VerifyTypeIs(Value::STRING, err))
+ return Value();
+ const std::string& new_ = args[2].string_value();
+
+ int64_t max = INT64_MAX;
+ if (args.size() > 3) {
+ if (!args[3].VerifyTypeIs(Value::INTEGER, err))
+ return Value();
+ max = args[3].int_value();
+ if (max <= 0) {
+ *err = Err(function, "Requested number of replacements is not positive.");
+ return Value();
+ }
+ }
+
+ int64_t n = 0;
+ std::string val(str);
+ size_t start_pos = 0;
+ while((start_pos = val.find(old, start_pos)) != std::string::npos) {
+ val.replace(start_pos, old.length(), new_);
+ start_pos += new_.length();
+ if (++n >= max)
+ break;
+ }
+ return Value(function, std::move(val));
+}
+
// -----------------------------------------------------------------------------
FunctionInfo::FunctionInfo()
@@ -1186,6 +1256,7 @@
INSERT_FUNCTION(SetDefaultToolchain, false)
INSERT_FUNCTION(SetSourcesAssignmentFilter, false)
INSERT_FUNCTION(SplitList, false)
+ INSERT_FUNCTION(StringReplace, false)
INSERT_FUNCTION(Template, false)
INSERT_FUNCTION(Tool, false)
INSERT_FUNCTION(Toolchain, false)
diff --git a/tools/gn/functions.h b/tools/gn/functions.h
index 4638276..5c707c4 100644
--- a/tools/gn/functions.h
+++ b/tools/gn/functions.h
@@ -329,6 +329,14 @@
BlockNode* block,
Err* err);
+extern const char kReplaceSubstr[];
+extern const char kReplaceSubstr_HelpShort[];
+extern const char kReplaceSubstr_Help[];
+Value RunReplaceSubstr(Scope* scope,
+ const FunctionCallNode* function,
+ const std::vector<Value>& args_list,
+ Err* err);
+
extern const char kTarget[];
extern const char kTarget_HelpShort[];
extern const char kTarget_Help[];
diff --git a/tools/gn/functions_unittest.cc b/tools/gn/functions_unittest.cc
index 8bd3e3a..589986c 100644
--- a/tools/gn/functions_unittest.cc
+++ b/tools/gn/functions_unittest.cc
@@ -125,6 +125,39 @@
setup.print_output());
}
+TEST(Functions, StringReplace) {
+ TestWithScope setup;
+
+ TestParseInput input(
+ // Replace all occurrences of string.
+ "out1 = string_replace(\"abbcc\", \"b\", \"d\")\n"
+ "print(out1)\n"
+
+ // Replace only the first occurrence.
+ "out2 = string_replace(\"abbcc\", \"b\", \"d\", 1)\n"
+ "print(out2)\n"
+
+ // Duplicate string to be replaced.
+ "out3 = string_replace(\"abbcc\", \"b\", \"bb\")\n"
+ "print(out3)\n"
+
+ // Handle overlapping occurrences.
+ "out4 = string_replace(\"aaa\", \"aa\", \"b\")\n"
+ "print(out4)\n");
+ ASSERT_FALSE(input.has_error());
+
+ Err err;
+ input.parsed()->Execute(setup.scope(), &err);
+ ASSERT_FALSE(err.has_error()) << err.message();
+
+ EXPECT_EQ(
+ "addcc\n"
+ "adbcc\n"
+ "abbbbcc\n"
+ "ba\n",
+ setup.print_output());
+}
+
TEST(Functions, DeclareArgs) {
TestWithScope setup;
Err err;