Skip to content

Commit

Permalink
Add a foreach function to GN.
Browse files Browse the repository at this point in the history
This adds a foreach function for iterating over lists. See documentation included in the help added in this patch.

BUG=
R=dpranke@chromium.org

Review URL: https://codereview.chromium.org/271403002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@270172 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
brettw@chromium.org committed May 13, 2014
1 parent e7b3c2c commit 92cafe5
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 0 deletions.
2 changes: 2 additions & 0 deletions tools/gn/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
124 changes: 124 additions & 0 deletions tools/gn/function_foreach.cc
Original file line number Diff line number Diff line change
@@ -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
53 changes: 53 additions & 0 deletions tools/gn/function_foreach_unittest.cc
Original file line number Diff line number Diff line change
@@ -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.
}
1 change: 1 addition & 0 deletions tools/gn/functions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions tools/gn/functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down
2 changes: 2 additions & 0 deletions tools/gn/gn.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
6 changes: 6 additions & 0 deletions tools/gn/scope.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions tools/gn/scope.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 92cafe5

Please sign in to comment.