summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--tools/gn/BUILD.gn2
-rw-r--r--tools/gn/function_foreach.cc124
-rw-r--r--tools/gn/function_foreach_unittest.cc53
-rw-r--r--tools/gn/functions.cc1
-rw-r--r--tools/gn/functions.h8
-rw-r--r--tools/gn/gn.gyp2
-rw-r--r--tools/gn/scope.cc6
-rw-r--r--tools/gn/scope.h4
8 files changed, 200 insertions, 0 deletions
diff --git a/tools/gn/BUILD.gn b/tools/gn/BUILD.gn
index 59058d3..f04bb61 100644
--- a/tools/gn/BUILD.gn
+++ b/tools/gn/BUILD.gn
@@ -52,6 +52,7 @@ static_library("gn_lib") {
"functions.h",
"functions_target.cc",
"function_exec_script.cc",
+ "function_foreach.cc",
"function_get_label_info.cc",
"function_get_target_outputs.cc",
"function_process_file_template.cc",
@@ -179,6 +180,7 @@ test("gn_unittests") {
"escape_unittest.cc",
"filesystem_utils_unittest.cc",
"file_template_unittest.cc",
+ "function_foreach_unittest.cc",
"function_get_label_info_unittest.cc",
"function_get_target_outputs_unittest.cc",
"function_rebase_path_unittest.cc",
diff --git a/tools/gn/function_foreach.cc b/tools/gn/function_foreach.cc
new file mode 100644
index 0000000..40f3892
--- /dev/null
+++ b/tools/gn/function_foreach.cc
@@ -0,0 +1,124 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/gn/err.h"
+#include "tools/gn/functions.h"
+#include "tools/gn/parse_tree.h"
+#include "tools/gn/scope.h"
+
+namespace functions {
+
+namespace {
+
+
+} // namespace
+
+const char kForEach[] = "foreach";
+const char kForEach_HelpShort[] =
+ "foreach: Iterate over a list.";
+const char kForEach_Help[] =
+ "foreach: Iterate over a list.\n"
+ "\n"
+ " foreach(<loop_var>, <list>) {\n"
+ " <loop contents>\n"
+ " }\n"
+ "\n"
+ " Executes the loop contents block over each item in the list,\n"
+ " assigning the loop_var to each item in sequence.\n"
+ "\n"
+ " The block does not introduce a new scope, so that variable assignments\n"
+ " inside the loop will be visible once the loop terminates.\n"
+ "\n"
+ " The loop variable will temporarily shadow any existing variables with\n"
+ " the same name for the duration of the loop. After the loop terminates\n"
+ " the loop variable will no longer be in scope, and the previous value\n"
+ " (if any) will be restored.\n"
+ "\n"
+ "Example\n"
+ "\n"
+ " mylist = [ \"a\", \"b\", \"c\" ]\n"
+ " foreach(i, mylist) {\n"
+ " print(i)\n"
+ " }\n"
+ "\n"
+ " Prints:\n"
+ " a\n"
+ " b\n"
+ " c\n";
+Value RunForEach(Scope* scope,
+ const FunctionCallNode* function,
+ const ListNode* args_list,
+ Err* err) {
+ const std::vector<const ParseNode*>& args_vector = args_list->contents();
+ if (args_vector.size() != 2) {
+ *err = Err(function, "Wrong number of arguments to foreach().",
+ "Expecting exactly two.");
+ return Value();
+ }
+
+ // Extract the loop variable.
+ const IdentifierNode* identifier = args_vector[0]->AsIdentifier();
+ if (!identifier) {
+ *err = Err(args_vector[0], "Expected an identifier for the loop var.");
+ return Value();
+ }
+ base::StringPiece loop_var(identifier->value().value());
+
+ // Extract the list, avoid a copy if it's an identifier (common case).
+ Value value_storage_for_exec; // Backing for list_value when we need to exec.
+ const Value* list_value = NULL;
+ const IdentifierNode* list_identifier = args_vector[1]->AsIdentifier();
+ if (list_identifier) {
+ list_value = scope->GetValue(list_identifier->value().value());
+ if (!list_value) {
+ *err = Err(args_vector[1], "Undefined identifier.");
+ return Value();
+ }
+ } else {
+ // Not an identifier, evaluate the node to get the result.
+ Scope list_exec_scope(scope);
+ value_storage_for_exec = args_vector[1]->Execute(scope, err);
+ if (err->has_error())
+ return Value();
+ list_value = &value_storage_for_exec;
+ }
+ if (!list_value->VerifyTypeIs(Value::LIST, err))
+ return Value();
+ const std::vector<Value>& list = list_value->list_value();
+
+ // Block to execute.
+ const BlockNode* block = function->block();
+ if (!block) {
+ *err = Err(function, "Expected { after foreach.");
+ return Value();
+ }
+
+ // If the loop variable was previously defined in this scope, save it so we
+ // can put it back after the loop is done.
+ const Value* old_loop_value_ptr = scope->GetValue(loop_var);
+ Value old_loop_value;
+ if (old_loop_value_ptr)
+ old_loop_value = *old_loop_value_ptr;
+
+ for (size_t i = 0; i < list.size(); i++) {
+ scope->SetValue(loop_var, list[i], function);
+ block->ExecuteBlockInScope(scope, err);
+ if (err->has_error())
+ return Value();
+ }
+
+ // Put back loop var.
+ if (old_loop_value_ptr) {
+ // Put back old value. Use the copy we made, rather than use the pointer,
+ // which will probably point to the new value now in the scope.
+ scope->SetValue(loop_var, old_loop_value, old_loop_value.origin());
+ } else {
+ // Loop variable was undefined before loop, delete it.
+ scope->RemoveIdentifier(loop_var);
+ }
+
+ return Value();
+}
+
+} // namespace functions
diff --git a/tools/gn/function_foreach_unittest.cc b/tools/gn/function_foreach_unittest.cc
new file mode 100644
index 0000000..15ca8a4
--- /dev/null
+++ b/tools/gn/function_foreach_unittest.cc
@@ -0,0 +1,53 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "tools/gn/test_with_scope.h"
+
+TEST(FunctionForeach, CollisionOnLoopVar) {
+ TestWithScope setup;
+ TestParseInput input(
+ "a = 5\n"
+ "i = 6\n"
+ "foreach(i, [1, 2, 3]) {\n" // Use same loop var name previously defined.
+ " print(\"$a $i\")\n"
+ " a = a + 1\n" // Test for side effects inside loop.
+ "}\n"
+ "print(\"$a $i\")"); // Make sure that i goes back to original value.
+ ASSERT_FALSE(input.has_error());
+
+ Err err;
+ input.parsed()->Execute(setup.scope(), &err);
+ ASSERT_FALSE(err.has_error()) << err.message();
+
+ EXPECT_EQ("5 1\n6 2\n7 3\n8 6\n", setup.print_output());
+}
+
+TEST(FunctionForeach, UniqueLoopVar) {
+ TestWithScope setup;
+ TestParseInput input_good(
+ "foreach(i, [1, 2, 3]) {\n"
+ " print(i)\n"
+ "}\n");
+ ASSERT_FALSE(input_good.has_error());
+
+ Err err;
+ input_good.parsed()->Execute(setup.scope(), &err);
+ ASSERT_FALSE(err.has_error()) << err.message();
+
+ EXPECT_EQ("1\n2\n3\n", setup.print_output());
+ setup.print_output().clear();
+
+ // Same thing but try to use the loop var after loop is done. It should be
+ // undefined and throw an error.
+ TestParseInput input_bad(
+ "foreach(i, [1, 2, 3]) {\n"
+ " print(i)\n"
+ "}\n"
+ "print(i)");
+ ASSERT_FALSE(input_bad.has_error()); // Should parse OK.
+
+ input_bad.parsed()->Execute(setup.scope(), &err);
+ ASSERT_TRUE(err.has_error()); // Shouldn't actually run.
+}
diff --git a/tools/gn/functions.cc b/tools/gn/functions.cc
index 29f7130..b2787a3 100644
--- a/tools/gn/functions.cc
+++ b/tools/gn/functions.cc
@@ -651,6 +651,7 @@ struct FunctionInfoInitializer {
INSERT_FUNCTION(DeclareArgs, false)
INSERT_FUNCTION(Defined, false)
INSERT_FUNCTION(ExecScript, false)
+ INSERT_FUNCTION(ForEach, false)
INSERT_FUNCTION(GetEnv, false)
INSERT_FUNCTION(GetLabelInfo, false)
INSERT_FUNCTION(GetTargetOutputs, false)
diff --git a/tools/gn/functions.h b/tools/gn/functions.h
index 6e0aa6d..4b1384a 100644
--- a/tools/gn/functions.h
+++ b/tools/gn/functions.h
@@ -139,6 +139,14 @@ Value RunExecutable(Scope* scope,
BlockNode* block,
Err* err);
+extern const char kForEach[];
+extern const char kForEach_HelpShort[];
+extern const char kForEach_Help[];
+Value RunForEach(Scope* scope,
+ const FunctionCallNode* function,
+ const ListNode* args_list,
+ Err* err);
+
extern const char kGetEnv[];
extern const char kGetEnv_HelpShort[];
extern const char kGetEnv_Help[];
diff --git a/tools/gn/gn.gyp b/tools/gn/gn.gyp
index 2cb3146..0ebe14e 100644
--- a/tools/gn/gn.gyp
+++ b/tools/gn/gn.gyp
@@ -56,6 +56,7 @@
'functions.cc',
'functions.h',
'function_exec_script.cc',
+ 'function_foreach.cc',
'function_get_label_info.cc',
'function_get_target_outputs.cc',
'function_process_file_template.cc',
@@ -180,6 +181,7 @@
'escape_unittest.cc',
'filesystem_utils_unittest.cc',
'file_template_unittest.cc',
+ 'function_foreach_unittest.cc',
'function_get_label_info_unittest.cc',
'function_get_target_outputs_unittest.cc',
'function_rebase_path_unittest.cc',
diff --git a/tools/gn/scope.cc b/tools/gn/scope.cc
index b2d8d66..ebaf0ef 100644
--- a/tools/gn/scope.cc
+++ b/tools/gn/scope.cc
@@ -123,6 +123,12 @@ Value* Scope::SetValue(const base::StringPiece& ident,
return &r.value;
}
+void Scope::RemoveIdentifier(const base::StringPiece& ident) {
+ RecordMap::iterator found = values_.find(ident);
+ if (found != values_.end())
+ values_.erase(found);
+}
+
bool Scope::AddTemplate(const std::string& name, const Template* templ) {
if (GetTemplate(name))
return false;
diff --git a/tools/gn/scope.h b/tools/gn/scope.h
index 1b0246d..53b3252 100644
--- a/tools/gn/scope.h
+++ b/tools/gn/scope.h
@@ -134,6 +134,10 @@ class Scope {
const Value& v,
const ParseNode* set_node);
+ // Removes the value with the given identifier if it exists on the current
+ // scope. This does not search recursive scopes. Does nothing if not found.
+ void RemoveIdentifier(const base::StringPiece& ident);
+
// Templates associated with this scope. A template can only be set once, so
// AddTemplate will fail and return false if a rule with that name already
// exists. GetTemplate returns NULL if the rule doesn't exist, and it will