// 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 #include #include "testing/gtest/include/gtest/gtest.h" #include "tools/gn/input_file.h" #include "tools/gn/parser.h" #include "tools/gn/tokenizer.h" namespace { bool GetTokens(const InputFile* input, std::vector* result) { result->clear(); Err err; *result = Tokenizer::Tokenize(input, &err); return !err.has_error(); } void DoParserPrintTest(const char* input, const char* expected) { std::vector tokens; InputFile input_file(SourceFile("/test")); input_file.SetContents(input); ASSERT_TRUE(GetTokens(&input_file, &tokens)); Err err; std::unique_ptr 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 tokens; InputFile input_file(SourceFile("/test")); input_file.SetContents(input); ASSERT_TRUE(GetTokens(&input_file, &tokens)); Err err; std::unique_ptr 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 tokens = Tokenizer::Tokenize(&input_file, &err); if (!err.has_error()) { std::unique_ptr 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 tokens = Tokenizer::Tokenize(&input_file, &err); if (!err.has_error()) { std::unique_ptr 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); }