forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
8 changed files
with
200 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters