// 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 "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<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; scoped_ptr<ParseNode> result = Parser::Parse(tokens, &err); 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; scoped_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()) { scoped_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().char_offset()); } // 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()) { scoped_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().char_offset()); } } // 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); } 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"); } 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, 6); DoExpressionErrorTest("[,]", 1, 2); DoExpressionErrorTest("[a,,]", 1, 4); } TEST(Parser, Assignment) { DoParserPrintTest("a=2", "BLOCK\n" " BINARY(=)\n" " IDENTIFIER(a)\n" " LITERAL(2)\n"); } TEST(Parser, Accessor) { DoParserPrintTest("a=b[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. " LITERAL(2)\n"); DoParserErrorTest("a = b[1][0]", 1, 5); } 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, NestedBlocks) { const char* input = "{cc_test(\"foo\") {{foo=1}\n{}}}"; const char* expected = "BLOCK\n" " BLOCK\n" " FUNCTION(cc_test)\n" " LIST\n" " LITERAL(\"foo\")\n" " BLOCK\n" " BLOCK\n" " BINARY(=)\n" " IDENTIFIER(foo)\n" " LITERAL(1)\n" " BLOCK\n"; DoParserPrintTest(input, expected); const char* input_with_newline = "{cc_test(\"foo\") {{foo=1}\n{}}}"; DoParserPrintTest(input_with_newline, 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, HangingIf) { DoParserErrorTest("if", 1, 1); } TEST(Parser, NegatingList) { DoParserErrorTest("executable(\"wee\") { sources =- [ \"foo.cc\" ] }", 1, 30); }