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.
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"
15 bool GetTokens(const InputFile
* input
, std::vector
<Token
>* result
) {
18 *result
= Tokenizer::Tokenize(input
, &err
);
19 return !err
.has_error();
22 void DoParserPrintTest(const char* input
, const char* expected
) {
23 std::vector
<Token
> tokens
;
24 InputFile
input_file(SourceFile("/test"));
25 input_file
.SetContents(input
);
26 ASSERT_TRUE(GetTokens(&input_file
, &tokens
));
29 scoped_ptr
<ParseNode
> result
= Parser::Parse(tokens
, &err
);
34 std::ostringstream collector
;
35 result
->Print(collector
, 0);
37 EXPECT_EQ(expected
, collector
.str());
40 void DoExpressionPrintTest(const char* input
, const char* expected
) {
41 std::vector
<Token
> tokens
;
42 InputFile
input_file(SourceFile("/test"));
43 input_file
.SetContents(input
);
44 ASSERT_TRUE(GetTokens(&input_file
, &tokens
));
47 scoped_ptr
<ParseNode
> result
= Parser::ParseExpression(tokens
, &err
);
50 std::ostringstream collector
;
51 result
->Print(collector
, 0);
53 EXPECT_EQ(expected
, collector
.str());
56 // Expects the tokenizer or parser to identify an error at the given line and
58 void DoParserErrorTest(const char* input
, int err_line
, int err_char
) {
59 InputFile
input_file(SourceFile("/test"));
60 input_file
.SetContents(input
);
63 std::vector
<Token
> tokens
= Tokenizer::Tokenize(&input_file
, &err
);
64 if (!err
.has_error()) {
65 scoped_ptr
<ParseNode
> result
= Parser::Parse(tokens
, &err
);
67 ASSERT_TRUE(err
.has_error());
70 EXPECT_EQ(err_line
, err
.location().line_number());
71 EXPECT_EQ(err_char
, err
.location().char_offset());
74 // Expects the tokenizer or parser to identify an error at the given line and
76 void DoExpressionErrorTest(const char* input
, int err_line
, int err_char
) {
77 InputFile
input_file(SourceFile("/test"));
78 input_file
.SetContents(input
);
81 std::vector
<Token
> tokens
= Tokenizer::Tokenize(&input_file
, &err
);
82 if (!err
.has_error()) {
83 scoped_ptr
<ParseNode
> result
= Parser::ParseExpression(tokens
, &err
);
85 ASSERT_TRUE(err
.has_error());
88 EXPECT_EQ(err_line
, err
.location().line_number());
89 EXPECT_EQ(err_char
, err
.location().char_offset());
94 TEST(Parser
, Literal
) {
95 DoExpressionPrintTest("5", "LITERAL(5)\n");
96 DoExpressionPrintTest("\"stuff\"", "LITERAL(\"stuff\")\n");
99 TEST(Parser
, BinaryOp
) {
100 // TODO(scottmg): The tokenizer is dumb, and treats "5-1" as two integers,
101 // not a binary operator between two positive integers.
102 DoExpressionPrintTest("5 - 1",
106 DoExpressionPrintTest("5+1",
110 DoExpressionPrintTest("5 - 1 - 2",
118 TEST(Parser
, FunctionCall
) {
119 DoExpressionPrintTest("foo()",
122 DoExpressionPrintTest("blah(1, 2)",
127 DoExpressionErrorTest("foo(1, 2,)", 1, 10);
128 DoExpressionErrorTest("foo(1 2)", 1, 7);
131 TEST(Parser
, ParenExpression
) {
132 const char* input
= "(foo(1)) + (a + (b - c) + d)";
133 const char* expected
=
145 DoExpressionPrintTest(input
, expected
);
146 DoExpressionErrorTest("(a +", 1, 4);
149 TEST(Parser
, OrderOfOperationsLeftAssociative
) {
150 const char* input
= "5 - 1 - 2\n";
151 const char* expected
=
157 DoExpressionPrintTest(input
, expected
);
160 TEST(Parser
, OrderOfOperationsEqualityBoolean
) {
162 "if (a == \"b\" && is_stuff) {\n"
165 const char* expected
=
172 " IDENTIFIER(is_stuff)\n"
176 " LITERAL(\"hai\")\n";
177 DoParserPrintTest(input
, expected
);
180 TEST(Parser
, UnaryOp
) {
181 DoExpressionPrintTest("!foo",
183 " IDENTIFIER(foo)\n");
185 // No contents for binary operator.
186 DoExpressionErrorTest("a = !", 1, 5);
190 DoExpressionPrintTest("[]", "LIST\n");
191 DoExpressionPrintTest("[1,asd,]",
194 " IDENTIFIER(asd)\n");
195 DoExpressionPrintTest("[1, 2+3 - foo]",
202 " IDENTIFIER(foo)\n");
203 DoExpressionPrintTest("[1,\n2,\n 3,\n 4]",
210 DoExpressionErrorTest("[a, 2+,]", 1, 7);
211 DoExpressionErrorTest("[,]", 1, 2);
212 DoExpressionErrorTest("[a,,]", 1, 4);
215 TEST(Parser
, Assignment
) {
216 DoParserPrintTest("a=2",
222 DoExpressionErrorTest("a = ", 1, 3);
225 TEST(Parser
, Accessor
) {
226 // Accessor indexing.
227 DoParserPrintTest("a=b[c+2]",
232 " b\n" // AccessorNode is a bit weird in that it holds
233 // a Token, not a ParseNode for the base.
237 DoParserErrorTest("a = b[1][0]", 1, 5);
240 DoParserPrintTest("a=b.c+2",
249 DoParserErrorTest("a = b.c.d", 1, 6); // Can't nest accessors (currently).
250 DoParserErrorTest("a.b = 5", 1, 1); // Can't assign to accessors (currently).
252 // Error at the bad dot in the RHS, not the + operator (crbug.com/472038).
253 DoParserErrorTest("foo(a + b.c.d)", 1, 10);
256 TEST(Parser
, Condition
) {
257 DoParserPrintTest("if(1) { a = 2 }",
266 DoParserPrintTest("if(1) { a = 2 } else if (0) { a = 3 } else { a = 4 }",
286 TEST(Parser
, OnlyCallAndAssignInBody
) {
287 DoParserErrorTest("[]", 1, 2);
288 DoParserErrorTest("3 + 4", 1, 5);
289 DoParserErrorTest("6 - 7", 1, 5);
290 DoParserErrorTest("if (1) { 5 } else { print(4) }", 1, 12);
293 TEST(Parser
, NoAssignmentInCondition
) {
294 DoParserErrorTest("if (a=2) {}", 1, 5);
297 TEST(Parser
, CompleteFunction
) {
299 "cc_test(\"foo\") {\n"
304 " dependencies = [\n"
308 const char* expected
=
310 " FUNCTION(cc_test)\n"
312 " LITERAL(\"foo\")\n"
315 " IDENTIFIER(sources)\n"
317 " LITERAL(\"foo.cc\")\n"
318 " LITERAL(\"foo.h\")\n"
320 " IDENTIFIER(dependencies)\n"
322 " LITERAL(\"base\")\n";
323 DoParserPrintTest(input
, expected
);
326 TEST(Parser
, FunctionWithConditional
) {
328 "cc_test(\"foo\") {\n"
329 " sources = [\"foo.cc\"]\n"
330 " if (OS == \"mac\") {\n"
331 " sources += \"bar.cc\"\n"
332 " } else if (OS == \"win\") {\n"
333 " sources -= [\"asd.cc\", \"foo.cc\"]\n"
335 " dependencies += [\"bar.cc\"]\n"
338 const char* expected
=
340 " FUNCTION(cc_test)\n"
342 " LITERAL(\"foo\")\n"
345 " IDENTIFIER(sources)\n"
347 " LITERAL(\"foo.cc\")\n"
351 " LITERAL(\"mac\")\n"
354 " IDENTIFIER(sources)\n"
355 " LITERAL(\"bar.cc\")\n"
359 " LITERAL(\"win\")\n"
362 " IDENTIFIER(sources)\n"
364 " LITERAL(\"asd.cc\")\n"
365 " LITERAL(\"foo.cc\")\n"
368 " IDENTIFIER(dependencies)\n"
370 " LITERAL(\"bar.cc\")\n";
371 DoParserPrintTest(input
, expected
);
374 TEST(Parser
, UnterminatedBlock
) {
375 DoParserErrorTest("stuff() {", 1, 9);
378 TEST(Parser
, BadlyTerminatedNumber
) {
379 DoParserErrorTest("1234z", 1, 5);
382 TEST(Parser
, NewlinesInUnusualPlaces
) {
396 TEST(Parser
, NewlinesInUnusualPlaces2
) {
404 "x =\ny if\n(1\n) {}",
423 TEST(Parser
, NewlineBeforeSubscript
) {
424 const char* input
= "a = b[1]";
425 const char* input_with_newline
= "a = b\n[1]";
426 const char* expected
=
441 TEST(Parser
, SequenceOfExpressions
) {
453 TEST(Parser
, BlockAfterFunction
) {
454 const char* input
= "func(\"stuff\") {\n}";
455 // TODO(scottmg): Do we really want these to mean different things?
456 const char* input_with_newline
= "func(\"stuff\")\n{\n}";
457 const char* expected
=
461 " LITERAL(\"stuff\")\n"
463 DoParserPrintTest(input
, expected
);
464 DoParserPrintTest(input_with_newline
, expected
);
467 TEST(Parser
, LongExpression
) {
468 const char* input
= "a = b + c && d || e";
469 const char* expected
=
480 DoParserPrintTest(input
, expected
);
483 TEST(Parser
, CommentsStandalone
) {
485 "# Toplevel comment.\n"
487 "executable(\"wee\") {}\n";
488 const char* expected
=
490 " BLOCK_COMMENT(# Toplevel comment.)\n"
491 " FUNCTION(executable)\n"
493 " LITERAL(\"wee\")\n"
495 DoParserPrintTest(input
, expected
);
498 TEST(Parser
, CommentsStandaloneEof
) {
500 "executable(\"wee\") {}\n"
502 const char* expected
=
504 " +AFTER_COMMENT(\"# EOF comment.\")\n"
505 " FUNCTION(executable)\n"
507 " LITERAL(\"wee\")\n"
509 DoParserPrintTest(input
, expected
);
512 TEST(Parser
, CommentsLineAttached
) {
514 "executable(\"wee\") {\n"
519 " # This file is special or something.\n"
523 const char* expected
=
525 " FUNCTION(executable)\n"
527 " LITERAL(\"wee\")\n"
530 " +BEFORE_COMMENT(\"# Some sources.\")\n"
531 " IDENTIFIER(sources)\n"
533 " LITERAL(\"stuff.cc\")\n"
534 " LITERAL(\"things.cc\")\n"
535 " LITERAL(\"another.cc\")\n"
536 " +BEFORE_COMMENT(\"# This file is special or something.\")\n";
537 DoParserPrintTest(input
, expected
);
540 TEST(Parser
, CommentsSuffix
) {
542 "executable(\"wee\") { # This is some stuff.\n"
543 "sources = [ \"a.cc\" # And another comment here.\n"
545 const char* expected
=
547 " FUNCTION(executable)\n"
549 " LITERAL(\"wee\")\n"
551 " +SUFFIX_COMMENT(\"# This is some stuff.\")\n"
554 " IDENTIFIER(sources)\n"
556 " LITERAL(\"a.cc\")\n"
557 " +SUFFIX_COMMENT(\"# And another comment here.\")\n";
558 DoParserPrintTest(input
, expected
);
561 TEST(Parser
, CommentsSuffixDifferentLine
) {
563 "executable(\"wee\") {\n"
564 " sources = [ \"a\",\n"
565 " \"b\" ] # Comment\n"
567 const char* expected
=
569 " FUNCTION(executable)\n"
571 " LITERAL(\"wee\")\n"
574 " IDENTIFIER(sources)\n"
579 " +SUFFIX_COMMENT(\"# Comment\")\n";
580 DoParserPrintTest(input
, expected
);
583 TEST(Parser
, CommentsSuffixMultiple
) {
585 "executable(\"wee\") {\n"
587 " \"a\", # This is a comment,\n"
588 " # and some more,\n" // Note that this is aligned with above.
592 const char* expected
=
594 " FUNCTION(executable)\n"
596 " LITERAL(\"wee\")\n"
599 " IDENTIFIER(sources)\n"
602 " +SUFFIX_COMMENT(\"# This is a comment,\")\n"
603 " +SUFFIX_COMMENT(\"# and some more,\")\n"
604 " +SUFFIX_COMMENT(\"# then the end.\")\n";
605 DoParserPrintTest(input
, expected
);
608 TEST(Parser
, CommentsConnectedInList
) {
612 " # Connected comment.\n"
616 const char* expected
=
619 " IDENTIFIER(defines)\n"
621 " LITERAL(\"WEE\")\n"
622 " +BEFORE_COMMENT(\"# Connected comment.\")\n"
623 " LITERAL(\"BLORPY\")\n";
624 DoParserPrintTest(input
, expected
);
627 TEST(Parser
, CommentsAtEndOfBlock
) {
630 " sources = [\"a.cc\"]\n"
631 " # Some comment at end.\n"
633 const char* expected
=
636 " IDENTIFIER(is_win)\n"
639 " IDENTIFIER(sources)\n"
641 " LITERAL(\"a.cc\")\n"
643 " +BEFORE_COMMENT(\"# Some comment at end.\")\n";
644 DoParserPrintTest(input
, expected
);
647 // TODO(scottmg): I could be convinced this is incorrect. It's not clear to me
648 // which thing this comment is intended to be attached to.
649 TEST(Parser
, CommentsEndOfBlockSingleLine
) {
651 "defines = [ # EOL defines.\n"
653 const char* expected
=
656 " IDENTIFIER(defines)\n"
657 " +SUFFIX_COMMENT(\"# EOL defines.\")\n"
659 DoParserPrintTest(input
, expected
);
662 TEST(Parser
, HangingIf
) {
663 DoParserErrorTest("if", 1, 1);
666 TEST(Parser
, NegatingList
) {
667 DoParserErrorTest("executable(\"wee\") { sources =- [ \"foo.cc\" ] }", 1, 30);
670 TEST(Parser
, ConditionNoBracesIf
) {
673 " foreach(foo, []) {}\n"
675 " foreach(bar, []) {}\n"
680 TEST(Parser
, ConditionNoBracesElse
) {
683 " foreach(foo, []) {}\n"
685 " foreach(bar, []) {}\n",
689 TEST(Parser
, ConditionNoBracesElseIf
) {
692 " foreach(foo, []) {}\n"
694 " foreach(bar, []) {}\n",
698 // Disallow standalone {} for introducing new scopes. These are ambiguous with
699 // target declarations (e.g. is:
701 // a function with an associated block, or a standalone function with a
702 // freestanding block.
703 TEST(Parser
, StandaloneBlock
) {