1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <iostream>
6#include <sstream>
7
8#include "testing/gtest/include/gtest/gtest.h"
9#include "tools/gn/input_file.h"
10#include "tools/gn/parser.h"
11#include "tools/gn/tokenizer.h"
12
13namespace {
14
15bool GetTokens(const InputFile* input, std::vector<Token>* result) {
16  result->clear();
17  Err err;
18  *result = Tokenizer::Tokenize(input, &err);
19  return !err.has_error();
20}
21
22bool IsIdentifierEqual(const ParseNode* node, const char* val) {
23  if (!node)
24    return false;
25  const IdentifierNode* ident = node->AsIdentifier();
26  if (!ident)
27    return false;
28  return ident->value().value() == val;
29}
30
31bool IsLiteralEqual(const ParseNode* node, const char* val) {
32  if (!node)
33    return false;
34  const LiteralNode* lit = node->AsLiteral();
35  if (!lit)
36    return false;
37  return lit->value().value() == val;
38}
39
40// Returns true if the given node as a simple assignment to a given value.
41bool IsAssignment(const ParseNode* node, const char* ident, const char* value) {
42  if (!node)
43    return false;
44  const BinaryOpNode* binary = node->AsBinaryOp();
45  if (!binary)
46    return false;
47  return binary->op().IsOperatorEqualTo("=") &&
48         IsIdentifierEqual(binary->left(), ident) &&
49         IsLiteralEqual(binary->right(), value);
50}
51
52// Returns true if the given node is a block with one assignment statement.
53bool IsBlockWithAssignment(const ParseNode* node,
54                           const char* ident, const char* value) {
55  if (!node)
56    return false;
57  const BlockNode* block = node->AsBlock();
58  if (!block)
59    return false;
60  if (block->statements().size() != 1)
61    return false;
62  return IsAssignment(block->statements()[0], ident, value);
63}
64
65void DoParserPrintTest(const char* input, const char* expected) {
66  std::vector<Token> tokens;
67  InputFile input_file(SourceFile("/test"));
68  input_file.SetContents(input);
69  ASSERT_TRUE(GetTokens(&input_file, &tokens));
70
71  Err err;
72  scoped_ptr<ParseNode> result = Parser::Parse(tokens, &err);
73  ASSERT_TRUE(result);
74
75  std::ostringstream collector;
76  result->Print(collector, 0);
77
78  EXPECT_EQ(expected, collector.str());
79}
80
81// Expects the tokenizer or parser to identify an error at the given line and
82// character.
83void DoParserErrorTest(const char* input, int err_line, int err_char) {
84  InputFile input_file(SourceFile("/test"));
85  input_file.SetContents(input);
86
87  Err err;
88  std::vector<Token> tokens = Tokenizer::Tokenize(&input_file, &err);
89  if (!err.has_error()) {
90    scoped_ptr<ParseNode> result = Parser::Parse(tokens, &err);
91    ASSERT_FALSE(result);
92    ASSERT_TRUE(err.has_error());
93  }
94
95  EXPECT_EQ(err_line, err.location().line_number());
96  EXPECT_EQ(err_char, err.location().char_offset());
97}
98
99}  // namespace
100
101TEST(Parser, BinaryOp) {
102  std::vector<Token> tokens;
103
104  // Simple set expression.
105  InputFile expr_input(SourceFile("/test"));
106  expr_input.SetContents("a=2");
107  ASSERT_TRUE(GetTokens(&expr_input, &tokens));
108  Err err;
109  Parser set(tokens, &err);
110  scoped_ptr<ParseNode> expr = set.ParseExpression();
111  ASSERT_TRUE(expr);
112
113  const BinaryOpNode* binary_op = expr->AsBinaryOp();
114  ASSERT_TRUE(binary_op);
115
116  EXPECT_TRUE(binary_op->left()->AsIdentifier());
117
118  EXPECT_TRUE(binary_op->op().type() == Token::OPERATOR);
119  EXPECT_TRUE(binary_op->op().value() == "=");
120
121  EXPECT_TRUE(binary_op->right()->AsLiteral());
122}
123
124TEST(Parser, Condition) {
125  std::vector<Token> tokens;
126
127  InputFile cond_input(SourceFile("/test"));
128  cond_input.SetContents("if(1) { a = 2 }");
129  ASSERT_TRUE(GetTokens(&cond_input, &tokens));
130  Err err;
131  Parser simple_if(tokens, &err);
132  scoped_ptr<ConditionNode> cond = simple_if.ParseCondition();
133  ASSERT_TRUE(cond);
134
135  EXPECT_TRUE(IsLiteralEqual(cond->condition(), "1"));
136  EXPECT_FALSE(cond->if_false());  // No else block.
137  EXPECT_TRUE(IsBlockWithAssignment(cond->if_true(), "a", "2"));
138
139  // Now try a complicated if/else if/else one.
140  InputFile complex_if_input(SourceFile("/test"));
141  complex_if_input.SetContents(
142      "if(1) { a = 2 } else if (0) { a = 3 } else { a = 4 }");
143  ASSERT_TRUE(GetTokens(&complex_if_input, &tokens));
144  Parser complex_if(tokens, &err);
145  cond = complex_if.ParseCondition();
146  ASSERT_TRUE(cond);
147
148  EXPECT_TRUE(IsLiteralEqual(cond->condition(), "1"));
149  EXPECT_TRUE(IsBlockWithAssignment(cond->if_true(), "a", "2"));
150
151  ASSERT_TRUE(cond->if_false());
152  const ConditionNode* nested_cond = cond->if_false()->AsConditionNode();
153  ASSERT_TRUE(nested_cond);
154  EXPECT_TRUE(IsLiteralEqual(nested_cond->condition(), "0"));
155  EXPECT_TRUE(IsBlockWithAssignment(nested_cond->if_true(), "a", "3"));
156  EXPECT_TRUE(IsBlockWithAssignment(nested_cond->if_false(), "a", "4"));
157}
158
159TEST(Parser, FunctionCall) {
160  const char* input = "foo(a, 1, 2,) bar()";
161  const char* expected =
162      "BLOCK\n"
163      " FUNCTION(foo)\n"
164      "  LIST\n"
165      "   IDENTIFIER(a)\n"
166      "   LITERAL(1)\n"
167      "   LITERAL(2)\n"
168      " FUNCTION(bar)\n"
169      "  LIST\n";
170  DoParserPrintTest(input, expected);
171}
172
173TEST(Parser, ParenExpression) {
174  const char* input = "(foo(1)) + (a + b)";
175  const char* expected =
176      "BLOCK\n"
177      " BINARY(+)\n"
178      "  FUNCTION(foo)\n"
179      "   LIST\n"
180      "    LITERAL(1)\n"
181      "  BINARY(+)\n"
182      "   IDENTIFIER(a)\n"
183      "   IDENTIFIER(b)\n";
184  DoParserPrintTest(input, expected);
185  DoParserErrorTest("(a +", 1, 4);
186}
187
188TEST(Parser, UnaryOp) {
189  std::vector<Token> tokens;
190
191  InputFile ident_input(SourceFile("/test"));
192  ident_input.SetContents("!foo");
193  ASSERT_TRUE(GetTokens(&ident_input, &tokens));
194  Err err;
195  Parser ident(tokens, &err);
196  scoped_ptr<UnaryOpNode> op = ident.ParseUnaryOp();
197
198  ASSERT_TRUE(op);
199  EXPECT_TRUE(op->op().type() == Token::OPERATOR);
200  EXPECT_TRUE(op->op().value() == "!");
201}
202
203TEST(Parser, CompleteFunction) {
204  const char* input =
205      "cc_test(\"foo\") {\n"
206      "  sources = [\n"
207      "    \"foo.cc\",\n"
208      "    \"foo.h\"\n"
209      "  ]\n"
210      "  dependencies = [\n"
211      "    \"base\"\n"
212      "  ]\n"
213      "}\n";
214  const char* expected =
215      "BLOCK\n"
216      " FUNCTION(cc_test)\n"
217      "  LIST\n"
218      "   LITERAL(\"foo\")\n"
219      "  BLOCK\n"
220      "   BINARY(=)\n"
221      "    IDENTIFIER(sources)\n"
222      "    LIST\n"
223      "     LITERAL(\"foo.cc\")\n"
224      "     LITERAL(\"foo.h\")\n"
225      "   BINARY(=)\n"
226      "    IDENTIFIER(dependencies)\n"
227      "    LIST\n"
228      "     LITERAL(\"base\")\n";
229  DoParserPrintTest(input, expected);
230}
231
232TEST(Parser, FunctionWithConditional) {
233  const char* input =
234      "cc_test(\"foo\") {\n"
235      "  sources = [\"foo.cc\"]\n"
236      "  if (OS == \"mac\") {\n"
237      "    sources += \"bar.cc\"\n"
238      "  } else if (OS == \"win\") {\n"
239      "    sources -= [\"asd.cc\", \"foo.cc\"]\n"
240      "  } else {\n"
241      "    dependencies += [\"bar.cc\"]\n"
242      "  }\n"
243      "}\n";
244  const char* expected =
245      "BLOCK\n"
246      " FUNCTION(cc_test)\n"
247      "  LIST\n"
248      "   LITERAL(\"foo\")\n"
249      "  BLOCK\n"
250      "   BINARY(=)\n"
251      "    IDENTIFIER(sources)\n"
252      "    LIST\n"
253      "     LITERAL(\"foo.cc\")\n"
254      "   CONDITION\n"
255      "    BINARY(==)\n"
256      "     IDENTIFIER(OS)\n"
257      "     LITERAL(\"mac\")\n"
258      "    BLOCK\n"
259      "     BINARY(+=)\n"
260      "      IDENTIFIER(sources)\n"
261      "      LITERAL(\"bar.cc\")\n"
262      "    CONDITION\n"
263      "     BINARY(==)\n"
264      "      IDENTIFIER(OS)\n"
265      "      LITERAL(\"win\")\n"
266      "     BLOCK\n"
267      "      BINARY(-=)\n"
268      "       IDENTIFIER(sources)\n"
269      "       LIST\n"
270      "        LITERAL(\"asd.cc\")\n"
271      "        LITERAL(\"foo.cc\")\n"
272      "     BLOCK\n"
273      "      BINARY(+=)\n"
274      "       IDENTIFIER(dependencies)\n"
275      "       LIST\n"
276      "        LITERAL(\"bar.cc\")\n";
277  DoParserPrintTest(input, expected);
278}
279
280TEST(Parser, NestedBlocks) {
281  const char* input = "{cc_test(\"foo\") {{foo=1}{}}}";
282  const char* expected =
283      "BLOCK\n"
284      " BLOCK\n"
285      "  FUNCTION(cc_test)\n"
286      "   LIST\n"
287      "    LITERAL(\"foo\")\n"
288      "   BLOCK\n"
289      "    BLOCK\n"
290      "     BINARY(=)\n"
291      "      IDENTIFIER(foo)\n"
292      "      LITERAL(1)\n"
293      "    BLOCK\n";
294  DoParserPrintTest(input, expected);
295}
296
297TEST(Parser, List) {
298  const char* input = "[] a = [1,asd,] b = [1, 2+3 - foo]";
299  const char* expected =
300      "BLOCK\n"
301      " LIST\n"
302      " BINARY(=)\n"
303      "  IDENTIFIER(a)\n"
304      "  LIST\n"
305      "   LITERAL(1)\n"
306      "   IDENTIFIER(asd)\n"
307      " BINARY(=)\n"
308      "  IDENTIFIER(b)\n"
309      "  LIST\n"
310      "   LITERAL(1)\n"
311      "   BINARY(+)\n"
312      "    LITERAL(2)\n"
313      "    BINARY(-)\n"
314      "     LITERAL(3)\n"
315      "     IDENTIFIER(foo)\n";
316  DoParserPrintTest(input, expected);
317
318  DoParserErrorTest("[a, 2+,]", 1, 7);
319  DoParserErrorTest("[,]", 1, 2);
320  DoParserErrorTest("[a,,]", 1, 4);
321}
322
323TEST(Parser, UnterminatedBlock) {
324  DoParserErrorTest("hello {", 1, 7);
325}
326
327TEST(Parser, BadlyTerminatedNumber) {
328  DoParserErrorTest("1234z", 1, 5);
329}
330