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;