| // 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; | |
| RenderToText(result->GetJSONNode(), 0, collector); | |
| 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; | |
| RenderToText(result->GetJSONNode(), 0, collector); | |
| 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); | |
| } |