| // 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 <iostream> |
| #include <sstream> |
| |
| #include "tools/gn/input_file.h" |
| #include "tools/gn/parser.h" |
| #include "tools/gn/tokenizer.h" |
| #include "util/test/test.h" |
| |
| namespace { |
| |
| bool GetTokens(const InputFile* input, std::vector<Token>* result) { |
| result->clear(); |
| Err err; |
| *result = Tokenizer::Tokenize(input, &err); |
| return !err.has_error(); |
| } |
| |
| void DoParserPrintTest(const char* input, const char* expected) { |
| std::vector<Token> tokens; |
| InputFile input_file(SourceFile("/test")); |
| input_file.SetContents(input); |
| ASSERT_TRUE(GetTokens(&input_file, &tokens)); |
| |
| Err err; |
| std::unique_ptr<ParseNode> result = Parser::Parse(tokens, &err); |
| if (!result) |
| err.PrintToStdout(); |
| ASSERT_TRUE(result); |
| |
| std::ostringstream collector; |
| result->Print(collector, 0); |
| |
| EXPECT_EQ(expected, collector.str()); |
| } |
| |
| void DoExpressionPrintTest(const char* input, const char* expected) { |
| std::vector<Token> tokens; |
| InputFile input_file(SourceFile("/test")); |
| input_file.SetContents(input); |
| ASSERT_TRUE(GetTokens(&input_file, &tokens)); |
| |
| Err err; |
| std::unique_ptr<ParseNode> result = Parser::ParseExpression(tokens, &err); |
| ASSERT_TRUE(result); |
| |
| std::ostringstream collector; |
| result->Print(collector, 0); |
| |
| EXPECT_EQ(expected, collector.str()); |
| } |
| |
| // Expects the tokenizer or parser to identify an error at the given line and |
| // character. |
| void DoParserErrorTest(const char* input, int err_line, int err_char) { |
| InputFile input_file(SourceFile("/test")); |
| input_file.SetContents(input); |
| |
| Err err; |
| std::vector<Token> tokens = Tokenizer::Tokenize(&input_file, &err); |
| if (!err.has_error()) { |
| std::unique_ptr<ParseNode> result = Parser::Parse(tokens, &err); |
| ASSERT_FALSE(result); |
| ASSERT_TRUE(err.has_error()); |
| } |
| |
| EXPECT_EQ(err_line, err.location().line_number()); |
| EXPECT_EQ(err_char, err.location().column_number()); |
| } |
| |
| // Expects the tokenizer or parser to identify an error at the given line and |
| // character. |
| void DoExpressionErrorTest(const char* input, int err_line, int err_char) { |
| InputFile input_file(SourceFile("/test")); |
| input_file.SetContents(input); |
| |
| Err err; |
| std::vector<Token> tokens = Tokenizer::Tokenize(&input_file, &err); |
| if (!err.has_error()) { |
| std::unique_ptr<ParseNode> result = Parser::ParseExpression(tokens, &err); |
| ASSERT_FALSE(result); |
| ASSERT_TRUE(err.has_error()); |
| } |
| |
| EXPECT_EQ(err_line, err.location().line_number()); |
| EXPECT_EQ(err_char, err.location().column_number()); |
| } |
| |
| } // namespace |
| |
| TEST(Parser, Literal) { |
| DoExpressionPrintTest("5", "LITERAL(5)\n"); |
| DoExpressionPrintTest("\"stuff\"", "LITERAL(\"stuff\")\n"); |
| } |
| |
| TEST(Parser, BinaryOp) { |
| // TODO(scottmg): The tokenizer is dumb, and treats "5-1" as two integers, |
| // not a binary operator between two positive integers. |
| DoExpressionPrintTest("5 - 1", |
| "BINARY(-)\n" |
| " LITERAL(5)\n" |
| " LITERAL(1)\n"); |
| DoExpressionPrintTest("5+1", |
| "BINARY(+)\n" |
| " LITERAL(5)\n" |
| " LITERAL(1)\n"); |
| DoExpressionPrintTest("5 - 1 - 2", |
| "BINARY(-)\n" |
| " BINARY(-)\n" |
| " LITERAL(5)\n" |
| " LITERAL(1)\n" |
| " LITERAL(2)\n"); |
| } |
| |
| TEST(Parser, FunctionCall) { |
| DoExpressionPrintTest("foo()", |
| "FUNCTION(foo)\n" |
| " LIST\n"); |
| DoExpressionPrintTest("blah(1, 2)", |
| "FUNCTION(blah)\n" |
| " LIST\n" |
| " LITERAL(1)\n" |
| " LITERAL(2)\n"); |
| DoExpressionErrorTest("foo(1, 2,)", 1, 10); |
| DoExpressionErrorTest("foo(1 2)", 1, 7); |
| } |
| |
| TEST(Parser, ParenExpression) { |
| const char* input = "(foo(1)) + (a + (b - c) + d)"; |
| const char* expected = |
| "BINARY(+)\n" |
| " FUNCTION(foo)\n" |
| " LIST\n" |
| " LITERAL(1)\n" |
| " BINARY(+)\n" |
| " BINARY(+)\n" |
| " IDENTIFIER(a)\n" |
| " BINARY(-)\n" |
| " IDENTIFIER(b)\n" |
| " IDENTIFIER(c)\n" |
| " IDENTIFIER(d)\n"; |
| DoExpressionPrintTest(input, expected); |
| DoExpressionErrorTest("(a +", 1, 4); |
| } |
| |
| TEST(Parser, OrderOfOperationsLeftAssociative) { |
| const char* input = "5 - 1 - 2\n"; |
| const char* expected = |
| "BINARY(-)\n" |
| " BINARY(-)\n" |
| " LITERAL(5)\n" |
| " LITERAL(1)\n" |
| " LITERAL(2)\n"; |
| DoExpressionPrintTest(input, expected); |
| } |
| |
| TEST(Parser, OrderOfOperationsEqualityBoolean) { |
| const char* input = |
| "if (a == \"b\" && is_stuff) {\n" |
| " print(\"hai\")\n" |
| "}\n"; |
| const char* expected = |
| "BLOCK\n" |
| " CONDITION\n" |
| " BINARY(&&)\n" |
| " BINARY(==)\n" |
| " IDENTIFIER(a)\n" |
| " LITERAL(\"b\")\n" |
| " IDENTIFIER(is_stuff)\n" |
| " BLOCK\n" |
| " FUNCTION(print)\n" |
| " LIST\n" |
| " LITERAL(\"hai\")\n"; |
| DoParserPrintTest(input, expected); |
| } |
| |
| TEST(Parser, UnaryOp) { |
| DoExpressionPrintTest("!foo", |
| "UNARY(!)\n" |
| " IDENTIFIER(foo)\n"); |
| |
| // No contents for binary operator. |
| DoExpressionErrorTest("a = !", 1, 5); |
| } |
| |
| TEST(Parser, List) { |
| DoExpressionPrintTest("[]", "LIST\n"); |
| DoExpressionPrintTest("[1,asd,]", |
| "LIST\n" |
| " LITERAL(1)\n" |
| " IDENTIFIER(asd)\n"); |
| DoExpressionPrintTest("[1, 2+3 - foo]", |
| "LIST\n" |
| " LITERAL(1)\n" |
| " BINARY(-)\n" |
| " BINARY(+)\n" |
| " LITERAL(2)\n" |
| " LITERAL(3)\n" |
| " IDENTIFIER(foo)\n"); |
| DoExpressionPrintTest("[1,\n2,\n 3,\n 4]", |
| "LIST\n" |
| " LITERAL(1)\n" |
| " LITERAL(2)\n" |
| " LITERAL(3)\n" |
| " LITERAL(4)\n"); |
| |
| DoExpressionErrorTest("[a, 2+,]", 1, 7); |
| DoExpressionErrorTest("[,]", 1, 2); |
| DoExpressionErrorTest("[a,,]", 1, 4); |
| } |
| |
| TEST(Parser, Assignment) { |
| DoParserPrintTest("a=2", |
| "BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(a)\n" |
| " LITERAL(2)\n"); |
| |
| DoExpressionErrorTest("a = ", 1, 3); |
| } |
| |
| TEST(Parser, Accessor) { |
| // Accessor indexing. |
| DoParserPrintTest("a=b[c+2]", |
| "BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(a)\n" |
| " ACCESSOR\n" |
| " b\n" // AccessorNode is a bit weird in that it holds |
| // a Token, not a ParseNode for the base. |
| " BINARY(+)\n" |
| " IDENTIFIER(c)\n" |
| " LITERAL(2)\n"); |
| DoParserErrorTest("a = b[1][0]", 1, 5); |
| |
| // Member accessors. |
| DoParserPrintTest("a=b.c+2", |
| "BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(a)\n" |
| " BINARY(+)\n" |
| " ACCESSOR\n" |
| " b\n" |
| " IDENTIFIER(c)\n" |
| " LITERAL(2)\n"); |
| DoParserPrintTest("a.b = 5", |
| "BLOCK\n" |
| " BINARY(=)\n" |
| " ACCESSOR\n" |
| " a\n" |
| " IDENTIFIER(b)\n" |
| " LITERAL(5)\n"); |
| DoParserErrorTest("a = b.c.d", 1, 6); // Can't nest accessors (currently). |
| |
| // Error at the bad dot in the RHS, not the + operator (crbug.com/472038). |
| DoParserErrorTest("foo(a + b.c.d)", 1, 10); |
| } |
| |
| TEST(Parser, Condition) { |
| DoParserPrintTest("if(1) { a = 2 }", |
| "BLOCK\n" |
| " CONDITION\n" |
| " LITERAL(1)\n" |
| " BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(a)\n" |
| " LITERAL(2)\n"); |
| |
| DoParserPrintTest("if(1) { a = 2 } else if (0) { a = 3 } else { a = 4 }", |
| "BLOCK\n" |
| " CONDITION\n" |
| " LITERAL(1)\n" |
| " BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(a)\n" |
| " LITERAL(2)\n" |
| " CONDITION\n" |
| " LITERAL(0)\n" |
| " BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(a)\n" |
| " LITERAL(3)\n" |
| " BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(a)\n" |
| " LITERAL(4)\n"); |
| } |
| |
| TEST(Parser, OnlyCallAndAssignInBody) { |
| DoParserErrorTest("[]", 1, 2); |
| DoParserErrorTest("3 + 4", 1, 5); |
| DoParserErrorTest("6 - 7", 1, 5); |
| DoParserErrorTest("if (1) { 5 } else { print(4) }", 1, 12); |
| } |
| |
| TEST(Parser, NoAssignmentInCondition) { |
| DoParserErrorTest("if (a=2) {}", 1, 5); |
| } |
| |
| TEST(Parser, CompleteFunction) { |
| const char* input = |
| "cc_test(\"foo\") {\n" |
| " sources = [\n" |
| " \"foo.cc\",\n" |
| " \"foo.h\"\n" |
| " ]\n" |
| " dependencies = [\n" |
| " \"base\"\n" |
| " ]\n" |
| "}\n"; |
| const char* expected = |
| "BLOCK\n" |
| " FUNCTION(cc_test)\n" |
| " LIST\n" |
| " LITERAL(\"foo\")\n" |
| " BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(sources)\n" |
| " LIST\n" |
| " LITERAL(\"foo.cc\")\n" |
| " LITERAL(\"foo.h\")\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(dependencies)\n" |
| " LIST\n" |
| " LITERAL(\"base\")\n"; |
| DoParserPrintTest(input, expected); |
| } |
| |
| TEST(Parser, FunctionWithConditional) { |
| const char* input = |
| "cc_test(\"foo\") {\n" |
| " sources = [\"foo.cc\"]\n" |
| " if (OS == \"mac\") {\n" |
| " sources += \"bar.cc\"\n" |
| " } else if (OS == \"win\") {\n" |
| " sources -= [\"asd.cc\", \"foo.cc\"]\n" |
| " } else {\n" |
| " dependencies += [\"bar.cc\"]\n" |
| " }\n" |
| "}\n"; |
| const char* expected = |
| "BLOCK\n" |
| " FUNCTION(cc_test)\n" |
| " LIST\n" |
| " LITERAL(\"foo\")\n" |
| " BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(sources)\n" |
| " LIST\n" |
| " LITERAL(\"foo.cc\")\n" |
| " CONDITION\n" |
| " BINARY(==)\n" |
| " IDENTIFIER(OS)\n" |
| " LITERAL(\"mac\")\n" |
| " BLOCK\n" |
| " BINARY(+=)\n" |
| " IDENTIFIER(sources)\n" |
| " LITERAL(\"bar.cc\")\n" |
| " CONDITION\n" |
| " BINARY(==)\n" |
| " IDENTIFIER(OS)\n" |
| " LITERAL(\"win\")\n" |
| " BLOCK\n" |
| " BINARY(-=)\n" |
| " IDENTIFIER(sources)\n" |
| " LIST\n" |
| " LITERAL(\"asd.cc\")\n" |
| " LITERAL(\"foo.cc\")\n" |
| " BLOCK\n" |
| " BINARY(+=)\n" |
| " IDENTIFIER(dependencies)\n" |
| " LIST\n" |
| " LITERAL(\"bar.cc\")\n"; |
| DoParserPrintTest(input, expected); |
| } |
| |
| TEST(Parser, UnterminatedBlock) { |
| DoParserErrorTest("stuff() {", 1, 9); |
| } |
| |
| TEST(Parser, BadlyTerminatedNumber) { |
| DoParserErrorTest("1234z", 1, 5); |
| } |
| |
| TEST(Parser, NewlinesInUnusualPlaces) { |
| DoParserPrintTest( |
| "if\n" |
| "(\n" |
| "a\n" |
| ")\n" |
| "{\n" |
| "}\n", |
| "BLOCK\n" |
| " CONDITION\n" |
| " IDENTIFIER(a)\n" |
| " BLOCK\n"); |
| } |
| |
| TEST(Parser, NewlinesInUnusualPlaces2) { |
| DoParserPrintTest("a\n=\n2\n", |
| "BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(a)\n" |
| " LITERAL(2)\n"); |
| DoParserPrintTest("x =\ny if\n(1\n) {}", |
| "BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(x)\n" |
| " IDENTIFIER(y)\n" |
| " CONDITION\n" |
| " LITERAL(1)\n" |
| " BLOCK\n"); |
| DoParserPrintTest("x = 3\n+2", |
| "BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(x)\n" |
| " BINARY(+)\n" |
| " LITERAL(3)\n" |
| " LITERAL(2)\n"); |
| } |
| |
| TEST(Parser, NewlineBeforeSubscript) { |
| const char* input = "a = b[1]"; |
| const char* input_with_newline = "a = b\n[1]"; |
| const char* expected = |
| "BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(a)\n" |
| " ACCESSOR\n" |
| " b\n" |
| " LITERAL(1)\n"; |
| DoParserPrintTest(input, expected); |
| DoParserPrintTest(input_with_newline, expected); |
| } |
| |
| TEST(Parser, SequenceOfExpressions) { |
| DoParserPrintTest("a = 1 b = 2", |
| "BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(a)\n" |
| " LITERAL(1)\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(b)\n" |
| " LITERAL(2)\n"); |
| } |
| |
| TEST(Parser, BlockAfterFunction) { |
| const char* input = "func(\"stuff\") {\n}"; |
| // TODO(scottmg): Do we really want these to mean different things? |
| const char* input_with_newline = "func(\"stuff\")\n{\n}"; |
| const char* expected = |
| "BLOCK\n" |
| " FUNCTION(func)\n" |
| " LIST\n" |
| " LITERAL(\"stuff\")\n" |
| " BLOCK\n"; |
| DoParserPrintTest(input, expected); |
| DoParserPrintTest(input_with_newline, expected); |
| } |
| |
| TEST(Parser, LongExpression) { |
| const char* input = "a = b + c && d || e"; |
| const char* expected = |
| "BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(a)\n" |
| " BINARY(||)\n" |
| " BINARY(&&)\n" |
| " BINARY(+)\n" |
| " IDENTIFIER(b)\n" |
| " IDENTIFIER(c)\n" |
| " IDENTIFIER(d)\n" |
| " IDENTIFIER(e)\n"; |
| DoParserPrintTest(input, expected); |
| } |
| |
| TEST(Parser, CommentsStandalone) { |
| const char* input = |
| "# Toplevel comment.\n" |
| "\n" |
| "executable(\"wee\") {}\n"; |
| const char* expected = |
| "BLOCK\n" |
| " BLOCK_COMMENT(# Toplevel comment.)\n" |
| " FUNCTION(executable)\n" |
| " LIST\n" |
| " LITERAL(\"wee\")\n" |
| " BLOCK\n"; |
| DoParserPrintTest(input, expected); |
| } |
| |
| TEST(Parser, CommentsStandaloneEof) { |
| const char* input = |
| "executable(\"wee\") {}\n" |
| "# EOF comment.\n"; |
| const char* expected = |
| "BLOCK\n" |
| " +AFTER_COMMENT(\"# EOF comment.\")\n" |
| " FUNCTION(executable)\n" |
| " LIST\n" |
| " LITERAL(\"wee\")\n" |
| " BLOCK\n"; |
| DoParserPrintTest(input, expected); |
| } |
| |
| TEST(Parser, CommentsLineAttached) { |
| const char* input = |
| "executable(\"wee\") {\n" |
| " # Some sources.\n" |
| " sources = [\n" |
| " \"stuff.cc\",\n" |
| " \"things.cc\",\n" |
| " # This file is special or something.\n" |
| " \"another.cc\",\n" |
| " ]\n" |
| "}\n"; |
| const char* expected = |
| "BLOCK\n" |
| " FUNCTION(executable)\n" |
| " LIST\n" |
| " LITERAL(\"wee\")\n" |
| " BLOCK\n" |
| " BINARY(=)\n" |
| " +BEFORE_COMMENT(\"# Some sources.\")\n" |
| " IDENTIFIER(sources)\n" |
| " LIST\n" |
| " LITERAL(\"stuff.cc\")\n" |
| " LITERAL(\"things.cc\")\n" |
| " LITERAL(\"another.cc\")\n" |
| " +BEFORE_COMMENT(\"# This file is special or something.\")\n"; |
| DoParserPrintTest(input, expected); |
| } |
| |
| TEST(Parser, CommentsSuffix) { |
| const char* input = |
| "executable(\"wee\") { # This is some stuff.\n" |
| "sources = [ \"a.cc\" # And another comment here.\n" |
| "] }"; |
| const char* expected = |
| "BLOCK\n" |
| " FUNCTION(executable)\n" |
| " LIST\n" |
| " LITERAL(\"wee\")\n" |
| " END())\n" |
| " +SUFFIX_COMMENT(\"# This is some stuff.\")\n" |
| " BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(sources)\n" |
| " LIST\n" |
| " LITERAL(\"a.cc\")\n" |
| " +SUFFIX_COMMENT(\"# And another comment here.\")\n"; |
| DoParserPrintTest(input, expected); |
| } |
| |
| TEST(Parser, CommentsSuffixDifferentLine) { |
| const char* input = |
| "executable(\"wee\") {\n" |
| " sources = [ \"a\",\n" |
| " \"b\" ] # Comment\n" |
| "}\n"; |
| const char* expected = |
| "BLOCK\n" |
| " FUNCTION(executable)\n" |
| " LIST\n" |
| " LITERAL(\"wee\")\n" |
| " BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(sources)\n" |
| " LIST\n" |
| " LITERAL(\"a\")\n" |
| " LITERAL(\"b\")\n" |
| " END(])\n" |
| " +SUFFIX_COMMENT(\"# Comment\")\n"; |
| DoParserPrintTest(input, expected); |
| } |
| |
| TEST(Parser, CommentsSuffixMultiple) { |
| const char* input = |
| "executable(\"wee\") {\n" |
| " sources = [\n" |
| " \"a\", # This is a comment,\n" |
| " # and some more,\n" // Note that this is aligned with above. |
| " # then the end.\n" |
| " ]\n" |
| "}\n"; |
| const char* expected = |
| "BLOCK\n" |
| " FUNCTION(executable)\n" |
| " LIST\n" |
| " LITERAL(\"wee\")\n" |
| " BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(sources)\n" |
| " LIST\n" |
| " LITERAL(\"a\")\n" |
| " +SUFFIX_COMMENT(\"# This is a comment,\")\n" |
| " +SUFFIX_COMMENT(\"# and some more,\")\n" |
| " +SUFFIX_COMMENT(\"# then the end.\")\n"; |
| DoParserPrintTest(input, expected); |
| } |
| |
| TEST(Parser, CommentsConnectedInList) { |
| const char* input = |
| "defines = [\n" |
| "\n" |
| " # Connected comment.\n" |
| " \"WEE\",\n" |
| " \"BLORPY\",\n" |
| "]\n"; |
| const char* expected = |
| "BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(defines)\n" |
| " LIST\n" |
| " LITERAL(\"WEE\")\n" |
| " +BEFORE_COMMENT(\"# Connected comment.\")\n" |
| " LITERAL(\"BLORPY\")\n"; |
| DoParserPrintTest(input, expected); |
| } |
| |
| TEST(Parser, CommentsAtEndOfBlock) { |
| const char* input = |
| "if (is_win) {\n" |
| " sources = [\"a.cc\"]\n" |
| " # Some comment at end.\n" |
| "}\n"; |
| const char* expected = |
| "BLOCK\n" |
| " CONDITION\n" |
| " IDENTIFIER(is_win)\n" |
| " BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(sources)\n" |
| " LIST\n" |
| " LITERAL(\"a.cc\")\n" |
| " END(})\n" |
| " +BEFORE_COMMENT(\"# Some comment at end.\")\n"; |
| DoParserPrintTest(input, expected); |
| } |
| |
| // TODO(scottmg): I could be convinced this is incorrect. It's not clear to me |
| // which thing this comment is intended to be attached to. |
| TEST(Parser, CommentsEndOfBlockSingleLine) { |
| const char* input = |
| "defines = [ # EOL defines.\n" |
| "]\n"; |
| const char* expected = |
| "BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(defines)\n" |
| " +SUFFIX_COMMENT(\"# EOL defines.\")\n" |
| " LIST\n"; |
| DoParserPrintTest(input, expected); |
| } |
| |
| TEST(Parser, HangingIf) { |
| DoParserErrorTest("if", 1, 1); |
| } |
| |
| TEST(Parser, NegatingList) { |
| DoParserErrorTest("executable(\"wee\") { sources =- [ \"foo.cc\" ] }", 1, 30); |
| } |
| |
| TEST(Parser, ConditionNoBracesIf) { |
| DoParserErrorTest( |
| "if (true)\n" |
| " foreach(foo, []) {}\n" |
| "else {\n" |
| " foreach(bar, []) {}\n" |
| "}\n", |
| 2, 3); |
| } |
| |
| TEST(Parser, ConditionNoBracesElse) { |
| DoParserErrorTest( |
| "if (true) {\n" |
| " foreach(foo, []) {}\n" |
| "} else\n" |
| " foreach(bar, []) {}\n", |
| 4, 3); |
| } |
| |
| TEST(Parser, ConditionNoBracesElseIf) { |
| DoParserErrorTest( |
| "if (true) {\n" |
| " foreach(foo, []) {}\n" |
| "} else if (true)\n" |
| " foreach(bar, []) {}\n", |
| 4, 3); |
| } |
| |
| // Disallow standalone {} for introducing new scopes. These are ambiguous with |
| // target declarations (e.g. is: |
| // foo("bar") {} |
| // a function with an associated block, or a standalone function with a |
| // freestanding block. |
| TEST(Parser, StandaloneBlock) { |
| // The error is reported at the end of the block when nothing is done |
| // with it. If we had said "a = { ..." then it would have been OK. |
| DoParserErrorTest( |
| "if (true) {\n" |
| "}\n" |
| "{\n" |
| " assert(false)\n" |
| "}\n", |
| 5, 1); |
| } |
| |
| TEST(Parser, BlockValues) { |
| const char* input = |
| "print({a = 1 b = 2}, 3)\n" |
| "a = { b = \"asd\" }"; |
| const char* expected = |
| "BLOCK\n" |
| " FUNCTION(print)\n" |
| " LIST\n" |
| " BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(a)\n" |
| " LITERAL(1)\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(b)\n" |
| " LITERAL(2)\n" |
| " LITERAL(3)\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(a)\n" |
| " BLOCK\n" |
| " BINARY(=)\n" |
| " IDENTIFIER(b)\n" |
| " LITERAL(\"asd\")\n"; |
| DoParserPrintTest(input, expected); |
| } |