diff options
-rw-r--r-- | tools/gn/BUILD.gn | 2 | ||||
-rw-r--r-- | tools/gn/function_foreach.cc | 124 | ||||
-rw-r--r-- | tools/gn/function_foreach_unittest.cc | 53 | ||||
-rw-r--r-- | tools/gn/functions.cc | 1 | ||||
-rw-r--r-- | tools/gn/functions.h | 8 | ||||
-rw-r--r-- | tools/gn/gn.gyp | 2 | ||||
-rw-r--r-- | tools/gn/scope.cc | 6 | ||||
-rw-r--r-- | tools/gn/scope.h | 4 |
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 |