330 lines
8.8 KiB
C++
330 lines
8.8 KiB
C++
|
// 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();
|
||
|
}
|
||
|
|
||
|
bool IsIdentifierEqual(const ParseNode* node, const char* val) {
|
||
|
if (!node)
|
||
|
return false;
|
||
|
const IdentifierNode* ident = node->AsIdentifier();
|
||
|
if (!ident)
|
||
|
return false;
|
||
|
return ident->value().value() == val;
|
||
|
}
|
||
|
|
||
|
bool IsLiteralEqual(const ParseNode* node, const char* val) {
|
||
|
if (!node)
|
||
|
return false;
|
||
|
const LiteralNode* lit = node->AsLiteral();
|
||
|
if (!lit)
|
||
|
return false;
|
||
|
return lit->value().value() == val;
|
||
|
}
|
||
|
|
||
|
// Returns true if the given node as a simple assignment to a given value.
|
||
|
bool IsAssignment(const ParseNode* node, const char* ident, const char* value) {
|
||
|
if (!node)
|
||
|
return false;
|
||
|
const BinaryOpNode* binary = node->AsBinaryOp();
|
||
|
if (!binary)
|
||
|
return false;
|
||
|
return binary->op().IsOperatorEqualTo("=") &&
|
||
|
IsIdentifierEqual(binary->left(), ident) &&
|
||
|
IsLiteralEqual(binary->right(), value);
|
||
|
}
|
||
|
|
||
|
// Returns true if the given node is a block with one assignment statement.
|
||
|
bool IsBlockWithAssignment(const ParseNode* node,
|
||
|
const char* ident, const char* value) {
|
||
|
if (!node)
|
||
|
return false;
|
||
|
const BlockNode* block = node->AsBlock();
|
||
|
if (!block)
|
||
|
return false;
|
||
|
if (block->statements().size() != 1)
|
||
|
return false;
|
||
|
return IsAssignment(block->statements()[0], ident, value);
|
||
|
}
|
||
|
|
||
|
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());
|
||
|
}
|
||
|
|
||
|
// 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());
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
TEST(Parser, BinaryOp) {
|
||
|
std::vector<Token> tokens;
|
||
|
|
||
|
// Simple set expression.
|
||
|
InputFile expr_input(SourceFile("/test"));
|
||
|
expr_input.SetContents("a=2");
|
||
|
ASSERT_TRUE(GetTokens(&expr_input, &tokens));
|
||
|
Err err;
|
||
|
Parser set(tokens, &err);
|
||
|
scoped_ptr<ParseNode> expr = set.ParseExpression();
|
||
|
ASSERT_TRUE(expr);
|
||
|
|
||
|
const BinaryOpNode* binary_op = expr->AsBinaryOp();
|
||
|
ASSERT_TRUE(binary_op);
|
||
|
|
||
|
EXPECT_TRUE(binary_op->left()->AsIdentifier());
|
||
|
|
||
|
EXPECT_TRUE(binary_op->op().type() == Token::OPERATOR);
|
||
|
EXPECT_TRUE(binary_op->op().value() == "=");
|
||
|
|
||
|
EXPECT_TRUE(binary_op->right()->AsLiteral());
|
||
|
}
|
||
|
|
||
|
TEST(Parser, Condition) {
|
||
|
std::vector<Token> tokens;
|
||
|
|
||
|
InputFile cond_input(SourceFile("/test"));
|
||
|
cond_input.SetContents("if(1) { a = 2 }");
|
||
|
ASSERT_TRUE(GetTokens(&cond_input, &tokens));
|
||
|
Err err;
|
||
|
Parser simple_if(tokens, &err);
|
||
|
scoped_ptr<ConditionNode> cond = simple_if.ParseCondition();
|
||
|
ASSERT_TRUE(cond);
|
||
|
|
||
|
EXPECT_TRUE(IsLiteralEqual(cond->condition(), "1"));
|
||
|
EXPECT_FALSE(cond->if_false()); // No else block.
|
||
|
EXPECT_TRUE(IsBlockWithAssignment(cond->if_true(), "a", "2"));
|
||
|
|
||
|
// Now try a complicated if/else if/else one.
|
||
|
InputFile complex_if_input(SourceFile("/test"));
|
||
|
complex_if_input.SetContents(
|
||
|
"if(1) { a = 2 } else if (0) { a = 3 } else { a = 4 }");
|
||
|
ASSERT_TRUE(GetTokens(&complex_if_input, &tokens));
|
||
|
Parser complex_if(tokens, &err);
|
||
|
cond = complex_if.ParseCondition();
|
||
|
ASSERT_TRUE(cond);
|
||
|
|
||
|
EXPECT_TRUE(IsLiteralEqual(cond->condition(), "1"));
|
||
|
EXPECT_TRUE(IsBlockWithAssignment(cond->if_true(), "a", "2"));
|
||
|
|
||
|
ASSERT_TRUE(cond->if_false());
|
||
|
const ConditionNode* nested_cond = cond->if_false()->AsConditionNode();
|
||
|
ASSERT_TRUE(nested_cond);
|
||
|
EXPECT_TRUE(IsLiteralEqual(nested_cond->condition(), "0"));
|
||
|
EXPECT_TRUE(IsBlockWithAssignment(nested_cond->if_true(), "a", "3"));
|
||
|
EXPECT_TRUE(IsBlockWithAssignment(nested_cond->if_false(), "a", "4"));
|
||
|
}
|
||
|
|
||
|
TEST(Parser, FunctionCall) {
|
||
|
const char* input = "foo(a, 1, 2,) bar()";
|
||
|
const char* expected =
|
||
|
"BLOCK\n"
|
||
|
" FUNCTION(foo)\n"
|
||
|
" LIST\n"
|
||
|
" IDENTIFIER(a)\n"
|
||
|
" LITERAL(1)\n"
|
||
|
" LITERAL(2)\n"
|
||
|
" FUNCTION(bar)\n"
|
||
|
" LIST\n";
|
||
|
DoParserPrintTest(input, expected);
|
||
|
}
|
||
|
|
||
|
TEST(Parser, ParenExpression) {
|
||
|
const char* input = "(foo(1)) + (a + b)";
|
||
|
const char* expected =
|
||
|
"BLOCK\n"
|
||
|
" BINARY(+)\n"
|
||
|
" FUNCTION(foo)\n"
|
||
|
" LIST\n"
|
||
|
" LITERAL(1)\n"
|
||
|
" BINARY(+)\n"
|
||
|
" IDENTIFIER(a)\n"
|
||
|
" IDENTIFIER(b)\n";
|
||
|
DoParserPrintTest(input, expected);
|
||
|
DoParserErrorTest("(a +", 1, 4);
|
||
|
}
|
||
|
|
||
|
TEST(Parser, UnaryOp) {
|
||
|
std::vector<Token> tokens;
|
||
|
|
||
|
InputFile ident_input(SourceFile("/test"));
|
||
|
ident_input.SetContents("!foo");
|
||
|
ASSERT_TRUE(GetTokens(&ident_input, &tokens));
|
||
|
Err err;
|
||
|
Parser ident(tokens, &err);
|
||
|
scoped_ptr<UnaryOpNode> op = ident.ParseUnaryOp();
|
||
|
|
||
|
ASSERT_TRUE(op);
|
||
|
EXPECT_TRUE(op->op().type() == Token::OPERATOR);
|
||
|
EXPECT_TRUE(op->op().value() == "!");
|
||
|
}
|
||
|
|
||
|
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}{}}}";
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
TEST(Parser, List) {
|
||
|
const char* input = "[] a = [1,asd,] b = [1, 2+3 - foo]";
|
||
|
const char* expected =
|
||
|
"BLOCK\n"
|
||
|
" LIST\n"
|
||
|
" BINARY(=)\n"
|
||
|
" IDENTIFIER(a)\n"
|
||
|
" LIST\n"
|
||
|
" LITERAL(1)\n"
|
||
|
" IDENTIFIER(asd)\n"
|
||
|
" BINARY(=)\n"
|
||
|
" IDENTIFIER(b)\n"
|
||
|
" LIST\n"
|
||
|
" LITERAL(1)\n"
|
||
|
" BINARY(+)\n"
|
||
|
" LITERAL(2)\n"
|
||
|
" BINARY(-)\n"
|
||
|
" LITERAL(3)\n"
|
||
|
" IDENTIFIER(foo)\n";
|
||
|
DoParserPrintTest(input, expected);
|
||
|
|
||
|
DoParserErrorTest("[a, 2+,]", 1, 7);
|
||
|
DoParserErrorTest("[,]", 1, 2);
|
||
|
DoParserErrorTest("[a,,]", 1, 4);
|
||
|
}
|
||
|
|
||
|
TEST(Parser, UnterminatedBlock) {
|
||
|
DoParserErrorTest("hello {", 1, 7);
|
||
|
}
|
||
|
|
||
|
TEST(Parser, BadlyTerminatedNumber) {
|
||
|
DoParserErrorTest("1234z", 1, 5);
|
||
|
}
|