Index: tools/gn/parser.cc |
diff --git a/tools/gn/parser.cc b/tools/gn/parser.cc |
index 7e5886f8950b3d4b4466aa34e966658ca05779ed..57a398e0187e50f03a7efa15443009fc9efb41ab 100644 |
--- a/tools/gn/parser.cc |
+++ b/tools/gn/parser.cc |
@@ -13,7 +13,7 @@ |
#include "tools/gn/token.h" |
const char kGrammar_Help[] = |
- "GN build language grammar\n" |
+ "Language and grammar for GN build files\n" |
"\n" |
"Tokens\n" |
"\n" |
@@ -79,6 +79,13 @@ const char kGrammar_Help[] = |
" To insert an arbitrary byte value, use $0xFF. For example, to\n" |
" insert a newline character: \"Line one$0x0ALine two\".\n" |
"\n" |
+ " An expansion will evaluate the variable following the '$' and insert\n" |
+ " a stringified version of it into the result. For example, to concat\n" |
+ " two path components with a slash separating them:\n" |
+ " \"$var_one/$var_two\"\n" |
+ " Use the \"${var_one}\" format to be explicitly deliniate the variable\n" |
+ " for otherwise-ambiguous cases.\n" |
+ "\n" |
"Punctuation\n" |
"\n" |
" The following character sequences represent punctuation:\n" |
@@ -95,19 +102,20 @@ const char kGrammar_Help[] = |
" File = StatementList .\n" |
"\n" |
" Statement = Assignment | Call | Condition .\n" |
- " Assignment = identifier AssignOp Expr .\n" |
+ " LValue = identifier | ArrayAccess | ScopeAccess .\n" |
+ " Assignment = LValue AssignOp Expr .\n" |
" Call = identifier \"(\" [ ExprList ] \")\" [ Block ] .\n" |
" Condition = \"if\" \"(\" Expr \")\" Block\n" |
" [ \"else\" ( Condition | Block ) ] .\n" |
" Block = \"{\" StatementList \"}\" .\n" |
" StatementList = { Statement } .\n" |
"\n" |
- " ArrayAccess = identifier \"[\" { identifier | integer } \"]\" .\n" |
+ " ArrayAccess = identifier \"[\" Expr \"]\" .\n" |
" ScopeAccess = identifier \".\" identifier .\n" |
" Expr = UnaryExpr | Expr BinaryOp Expr .\n" |
" UnaryExpr = PrimaryExpr | UnaryOp UnaryExpr .\n" |
" PrimaryExpr = identifier | integer | string | Call\n" |
- " | ArrayAccess | ScopeAccess\n" |
+ " | ArrayAccess | ScopeAccess | Block\n" |
" | \"(\" Expr \")\"\n" |
" | \"[\" [ ExprList [ \",\" ] ] \"]\" .\n" |
" ExprList = Expr { \",\" Expr } .\n" |
@@ -120,7 +128,95 @@ const char kGrammar_Help[] = |
" | \"&&\"\n" |
" | \"||\" . // lowest priority\n" |
"\n" |
- " All binary operators are left-associative.\n"; |
+ " All binary operators are left-associative.\n" |
+ "\n" |
+ "Types\n" |
+ "\n" |
+ " The GN language is dynamically typed. The following types are used:\n" |
+ "\n" |
+ " - Boolean: Uses the keywords \"true\" and \"false\". There is no\n" |
+ " implicit conversion between booleans and integers.\n" |
+ "\n" |
+ " - Integers: All numbers in GN are signed 64-bit integers.\n" |
+ "\n" |
+ " - Strings: Strings are 8-bit with no enforced encoding. When a string\n" |
+ " is used to interact with other systems with particular encodings\n" |
+ " (like the Windows and Mac filesystems) it is assumed to be UTF-8.\n" |
+ " See \"String literals\" above for more.\n" |
+ "\n" |
+ " - Lists: Lists are arbitrary-length ordered lists of values. See\n" |
+ " \"Lists\" below for more.\n" |
+ "\n" |
+ " - Scopes: Scopes are like dictionaries that use variable names for\n" |
+ " keys. See \"Scopes\" below for more.\n" |
+ "\n" |
+ "Lists\n" |
+ "\n" |
+ " Lists are created with [] and using commas to separate items:\n" |
+ "\n" |
+ " mylist = [ 0, 1, 2, \"some string\" ]\n" |
+ "\n" |
+ " A comma after the last item is optional. Lists are dereferenced using\n" |
+ " 0-based indexing:\n" |
+ "\n" |
+ " mylist[0] += 1\n" |
+ " var = mylist[2]\n" |
+ "\n" |
+ " Lists can be concatenated using the '+' and '+=' operators. Bare\n" |
+ " values can not be concatenated with lists, to add a single item,\n" |
+ " it must be put into a list of length one.\n" |
+ "\n" |
+ " Items can be removed from lists using the '-' and '-=' operators.\n" |
+ " This will remove all occurrences of every item in the right-hand list\n" |
+ " from the left-hand list. It is an error to remove an item not in the\n" |
+ " list. This is to prevent common typos and to detect dead code that\n" |
+ " is removing things that no longer apply.\n" |
+ "\n" |
+ " It is an error to use '=' to replace a nonempty list with another\n" |
+ " nonempty list. This is to prevent accidentally overwriting data\n" |
+ " when in most cases '+=' was intended. To overwrite a list on purpose,\n" |
+ " first assign it to the empty list:\n" |
+ "\n" |
+ " mylist = []\n" |
+ " mylist = otherlist\n" |
+ "\n" |
+ " When assigning to a list named 'sources' using '=' or '+=', list\n" |
+ " items may be automatically filtered out.\n" |
+ " See \"gn help set_sources_assignment_filter\" for more.\n" |
+ "\n" |
+ "Scopes\n" |
+ "\n" |
+ " All execution happens in the context of a scope which holds the\n" |
+ " current state (like variables). With the exception of loops and\n" |
+ " conditions, '{' introduces a new scope that has a parent reference to\n" |
+ " the old scope.\n" |
+ "\n" |
+ " Variable reads recursively search all nested scopes until the\n" |
+ " variable is found or there are no more scopes. Variable writes always\n" |
+ " go into the current scope. This means that after the closing '}'\n" |
+ " (again excepting loops and conditions), all local variables will be\n" |
+ " restored to the previous values. This also means that \"foo = foo\"\n" |
+ " can do useful work by copying a variable into the current scope that\n" |
+ " was defined in a containing scope.\n" |
+ "\n" |
+ " Scopes can also be assigned to variables. Such scopes can be created\n" |
+ " by functions like exec_script, when invoking a template (the template\n" |
+ " code refers to the variables set by the invoking code by the\n" |
+ " implicitly-created \"invoker\" scope), or explicitly like:\n" |
+ "\n" |
+ " empty_scope = {}\n" |
+ " myvalues = {\n" |
+ " foo = 21\n" |
+ " bar = \"something\"\n" |
+ " }\n" |
+ "\n" |
+ " Inside such a scope definition can be any GN code including\n" |
+ " conditionals and function calls. After the close of the scope, it will\n" |
+ " contain all variables explicitly set by the code contained inside it.\n" |
+ " After this, the values can be read, modified, or added to:\n" |
+ "\n" |
+ " myvalues.foo += 2\n" |
+ " empty_scope.new_thing = [ 1, 2, 3 ]\n"; |
enum Precedence { |
PRECEDENCE_ASSIGNMENT = 1, // Lowest precedence. |
@@ -172,7 +268,7 @@ ParserHelper Parser::expressions_[] = { |
{nullptr, nullptr, -1}, // RIGHT_PAREN |
{&Parser::List, &Parser::Subscript, PRECEDENCE_CALL}, // LEFT_BRACKET |
{nullptr, nullptr, -1}, // RIGHT_BRACKET |
- {nullptr, nullptr, -1}, // LEFT_BRACE |
+ {&Parser::Block, nullptr, -1}, // LEFT_BRACE |
{nullptr, nullptr, -1}, // RIGHT_BRACE |
{nullptr, nullptr, -1}, // IF |
{nullptr, nullptr, -1}, // ELSE |
@@ -356,6 +452,12 @@ std::unique_ptr<ParseNode> Parser::ParseExpression(int precedence) { |
return left; |
} |
+std::unique_ptr<ParseNode> Parser::Block(Token token) { |
+ // This entrypoing into ParseBlock means its part of an expression and we |
+ // always want the result. |
+ return ParseBlock(token, BlockNode::RETURNS_SCOPE); |
+} |
+ |
std::unique_ptr<ParseNode> Parser::Literal(Token token) { |
return base::WrapUnique(new LiteralNode(token)); |
} |
@@ -441,7 +543,7 @@ std::unique_ptr<ParseNode> Parser::IdentifierOrCall( |
} |
// Optionally with a scope. |
if (LookAhead(Token::LEFT_BRACE)) { |
- block = ParseBlock(); |
+ block = ParseBlock(Consume(), BlockNode::DISCARDS_RESULT); |
if (has_error()) |
return std::unique_ptr<ParseNode>(); |
} |
@@ -461,8 +563,10 @@ std::unique_ptr<ParseNode> Parser::IdentifierOrCall( |
std::unique_ptr<ParseNode> Parser::Assignment(std::unique_ptr<ParseNode> left, |
Token token) { |
- if (left->AsIdentifier() == nullptr) { |
- *err_ = Err(left.get(), "Left-hand side of assignment must be identifier."); |
+ if (left->AsIdentifier() == nullptr && left->AsAccessor() == nullptr) { |
+ *err_ = Err(left.get(), |
+ "The left-hand side of an assignment must be an identifier, " |
+ "scope access, or array access."); |
return std::unique_ptr<ParseNode>(); |
} |
std::unique_ptr<ParseNode> value = ParseExpression(PRECEDENCE_ASSIGNMENT); |
@@ -567,7 +671,7 @@ std::unique_ptr<ListNode> Parser::ParseList(Token start_token, |
} |
std::unique_ptr<ParseNode> Parser::ParseFile() { |
- std::unique_ptr<BlockNode> file(new BlockNode); |
+ std::unique_ptr<BlockNode> file(new BlockNode(BlockNode::DISCARDS_RESULT)); |
for (;;) { |
if (at_end()) |
break; |
@@ -611,13 +715,13 @@ std::unique_ptr<ParseNode> Parser::ParseStatement() { |
} |
} |
-std::unique_ptr<BlockNode> Parser::ParseBlock() { |
- Token begin_token = |
- Consume(Token::LEFT_BRACE, "Expected '{' to start a block."); |
+std::unique_ptr<BlockNode> Parser::ParseBlock( |
+ Token begin_brace, |
+ BlockNode::ResultMode result_mode) { |
if (has_error()) |
return std::unique_ptr<BlockNode>(); |
- std::unique_ptr<BlockNode> block(new BlockNode); |
- block->set_begin_token(begin_token); |
+ std::unique_ptr<BlockNode> block(new BlockNode(result_mode)); |
+ block->set_begin_token(begin_brace); |
for (;;) { |
if (LookAhead(Token::RIGHT_BRACE)) { |
@@ -641,10 +745,13 @@ std::unique_ptr<ParseNode> Parser::ParseCondition() { |
if (IsAssignment(condition->condition())) |
*err_ = Err(condition->condition(), "Assignment not allowed in 'if'."); |
Consume(Token::RIGHT_PAREN, "Expected ')' after condition of 'if'."); |
- condition->set_if_true(ParseBlock()); |
+ condition->set_if_true(ParseBlock( |
+ Consume(Token::LEFT_BRACE, "Expected '{' to start 'if' block."), |
+ BlockNode::DISCARDS_RESULT)); |
if (Match(Token::ELSE)) { |
if (LookAhead(Token::LEFT_BRACE)) { |
- condition->set_if_false(ParseBlock()); |
+ condition->set_if_false(ParseBlock(Consume(), |
+ BlockNode::DISCARDS_RESULT)); |
} else if (LookAhead(Token::IF)) { |
condition->set_if_false(ParseStatement()); |
} else { |