Add ICU message format support
[chromium-blink-merge.git] / tools / gn / parser_unittest.cc
blobf5adb04ac679e4c269182d50ea2869320f2b0833
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.
5 #include <iostream>
6 #include <sstream>
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"
13 namespace {
15 bool 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();
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));
28 Err err;
29 scoped_ptr<ParseNode> result = Parser::Parse(tokens, &err);
30 if (!result)
31 err.PrintToStdout();
32 ASSERT_TRUE(result);
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));
46 Err err;
47 scoped_ptr<ParseNode> result = Parser::ParseExpression(tokens, &err);
48 ASSERT_TRUE(result);
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
57 // character.
58 void DoParserErrorTest(const char* input, int err_line, int err_char) {
59 InputFile input_file(SourceFile("/test"));
60 input_file.SetContents(input);
62 Err err;
63 std::vector<Token> tokens = Tokenizer::Tokenize(&input_file, &err);
64 if (!err.has_error()) {
65 scoped_ptr<ParseNode> result = Parser::Parse(tokens, &err);
66 ASSERT_FALSE(result);
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
75 // character.
76 void DoExpressionErrorTest(const char* input, int err_line, int err_char) {
77 InputFile input_file(SourceFile("/test"));
78 input_file.SetContents(input);
80 Err err;
81 std::vector<Token> tokens = Tokenizer::Tokenize(&input_file, &err);
82 if (!err.has_error()) {
83 scoped_ptr<ParseNode> result = Parser::ParseExpression(tokens, &err);
84 ASSERT_FALSE(result);
85 ASSERT_TRUE(err.has_error());
88 EXPECT_EQ(err_line, err.location().line_number());
89 EXPECT_EQ(err_char, err.location().char_offset());
92 } // namespace
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",
103 "BINARY(-)\n"
104 " LITERAL(5)\n"
105 " LITERAL(1)\n");
106 DoExpressionPrintTest("5+1",
107 "BINARY(+)\n"
108 " LITERAL(5)\n"
109 " LITERAL(1)\n");
110 DoExpressionPrintTest("5 - 1 - 2",
111 "BINARY(-)\n"
112 " BINARY(-)\n"
113 " LITERAL(5)\n"
114 " LITERAL(1)\n"
115 " LITERAL(2)\n");
118 TEST(Parser, FunctionCall) {
119 DoExpressionPrintTest("foo()",
120 "FUNCTION(foo)\n"
121 " LIST\n");
122 DoExpressionPrintTest("blah(1, 2)",
123 "FUNCTION(blah)\n"
124 " LIST\n"
125 " LITERAL(1)\n"
126 " LITERAL(2)\n");
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 =
134 "BINARY(+)\n"
135 " FUNCTION(foo)\n"
136 " LIST\n"
137 " LITERAL(1)\n"
138 " BINARY(+)\n"
139 " BINARY(+)\n"
140 " IDENTIFIER(a)\n"
141 " BINARY(-)\n"
142 " IDENTIFIER(b)\n"
143 " IDENTIFIER(c)\n"
144 " IDENTIFIER(d)\n";
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 =
152 "BINARY(-)\n"
153 " BINARY(-)\n"
154 " LITERAL(5)\n"
155 " LITERAL(1)\n"
156 " LITERAL(2)\n";
157 DoExpressionPrintTest(input, expected);
160 TEST(Parser, OrderOfOperationsEqualityBoolean) {
161 const char* input =
162 "if (a == \"b\" && is_stuff) {\n"
163 " print(\"hai\")\n"
164 "}\n";
165 const char* expected =
166 "BLOCK\n"
167 " CONDITION\n"
168 " BINARY(&&)\n"
169 " BINARY(==)\n"
170 " IDENTIFIER(a)\n"
171 " LITERAL(\"b\")\n"
172 " IDENTIFIER(is_stuff)\n"
173 " BLOCK\n"
174 " FUNCTION(print)\n"
175 " LIST\n"
176 " LITERAL(\"hai\")\n";
177 DoParserPrintTest(input, expected);
180 TEST(Parser, UnaryOp) {
181 DoExpressionPrintTest("!foo",
182 "UNARY(!)\n"
183 " IDENTIFIER(foo)\n");
186 TEST(Parser, List) {
187 DoExpressionPrintTest("[]", "LIST\n");
188 DoExpressionPrintTest("[1,asd,]",
189 "LIST\n"
190 " LITERAL(1)\n"
191 " IDENTIFIER(asd)\n");
192 DoExpressionPrintTest("[1, 2+3 - foo]",
193 "LIST\n"
194 " LITERAL(1)\n"
195 " BINARY(-)\n"
196 " BINARY(+)\n"
197 " LITERAL(2)\n"
198 " LITERAL(3)\n"
199 " IDENTIFIER(foo)\n");
200 DoExpressionPrintTest("[1,\n2,\n 3,\n 4]",
201 "LIST\n"
202 " LITERAL(1)\n"
203 " LITERAL(2)\n"
204 " LITERAL(3)\n"
205 " LITERAL(4)\n");
207 DoExpressionErrorTest("[a, 2+,]", 1, 7);
208 DoExpressionErrorTest("[,]", 1, 2);
209 DoExpressionErrorTest("[a,,]", 1, 4);
212 TEST(Parser, Assignment) {
213 DoParserPrintTest("a=2",
214 "BLOCK\n"
215 " BINARY(=)\n"
216 " IDENTIFIER(a)\n"
217 " LITERAL(2)\n");
220 TEST(Parser, Accessor) {
221 // Accessor indexing.
222 DoParserPrintTest("a=b[c+2]",
223 "BLOCK\n"
224 " BINARY(=)\n"
225 " IDENTIFIER(a)\n"
226 " ACCESSOR\n"
227 " b\n" // AccessorNode is a bit weird in that it holds
228 // a Token, not a ParseNode for the base.
229 " BINARY(+)\n"
230 " IDENTIFIER(c)\n"
231 " LITERAL(2)\n");
232 DoParserErrorTest("a = b[1][0]", 1, 5);
234 // Member accessors.
235 DoParserPrintTest("a=b.c+2",
236 "BLOCK\n"
237 " BINARY(=)\n"
238 " IDENTIFIER(a)\n"
239 " BINARY(+)\n"
240 " ACCESSOR\n"
241 " b\n"
242 " IDENTIFIER(c)\n"
243 " LITERAL(2)\n");
244 DoParserErrorTest("a = b.c.d", 1, 6); // Can't nest accessors (currently).
245 DoParserErrorTest("a.b = 5", 1, 1); // Can't assign to accessors (currently).
247 // Error at the bad dot in the RHS, not the + operator (crbug.com/472038).
248 DoParserErrorTest("foo(a + b.c.d)", 1, 10);
251 TEST(Parser, Condition) {
252 DoParserPrintTest("if(1) { a = 2 }",
253 "BLOCK\n"
254 " CONDITION\n"
255 " LITERAL(1)\n"
256 " BLOCK\n"
257 " BINARY(=)\n"
258 " IDENTIFIER(a)\n"
259 " LITERAL(2)\n");
261 DoParserPrintTest("if(1) { a = 2 } else if (0) { a = 3 } else { a = 4 }",
262 "BLOCK\n"
263 " CONDITION\n"
264 " LITERAL(1)\n"
265 " BLOCK\n"
266 " BINARY(=)\n"
267 " IDENTIFIER(a)\n"
268 " LITERAL(2)\n"
269 " CONDITION\n"
270 " LITERAL(0)\n"
271 " BLOCK\n"
272 " BINARY(=)\n"
273 " IDENTIFIER(a)\n"
274 " LITERAL(3)\n"
275 " BLOCK\n"
276 " BINARY(=)\n"
277 " IDENTIFIER(a)\n"
278 " LITERAL(4)\n");
281 TEST(Parser, OnlyCallAndAssignInBody) {
282 DoParserErrorTest("[]", 1, 2);
283 DoParserErrorTest("3 + 4", 1, 5);
284 DoParserErrorTest("6 - 7", 1, 5);
285 DoParserErrorTest("if (1) { 5 } else { print(4) }", 1, 12);
288 TEST(Parser, NoAssignmentInCondition) {
289 DoParserErrorTest("if (a=2) {}", 1, 5);
292 TEST(Parser, CompleteFunction) {
293 const char* input =
294 "cc_test(\"foo\") {\n"
295 " sources = [\n"
296 " \"foo.cc\",\n"
297 " \"foo.h\"\n"
298 " ]\n"
299 " dependencies = [\n"
300 " \"base\"\n"
301 " ]\n"
302 "}\n";
303 const char* expected =
304 "BLOCK\n"
305 " FUNCTION(cc_test)\n"
306 " LIST\n"
307 " LITERAL(\"foo\")\n"
308 " BLOCK\n"
309 " BINARY(=)\n"
310 " IDENTIFIER(sources)\n"
311 " LIST\n"
312 " LITERAL(\"foo.cc\")\n"
313 " LITERAL(\"foo.h\")\n"
314 " BINARY(=)\n"
315 " IDENTIFIER(dependencies)\n"
316 " LIST\n"
317 " LITERAL(\"base\")\n";
318 DoParserPrintTest(input, expected);
321 TEST(Parser, FunctionWithConditional) {
322 const char* input =
323 "cc_test(\"foo\") {\n"
324 " sources = [\"foo.cc\"]\n"
325 " if (OS == \"mac\") {\n"
326 " sources += \"bar.cc\"\n"
327 " } else if (OS == \"win\") {\n"
328 " sources -= [\"asd.cc\", \"foo.cc\"]\n"
329 " } else {\n"
330 " dependencies += [\"bar.cc\"]\n"
331 " }\n"
332 "}\n";
333 const char* expected =
334 "BLOCK\n"
335 " FUNCTION(cc_test)\n"
336 " LIST\n"
337 " LITERAL(\"foo\")\n"
338 " BLOCK\n"
339 " BINARY(=)\n"
340 " IDENTIFIER(sources)\n"
341 " LIST\n"
342 " LITERAL(\"foo.cc\")\n"
343 " CONDITION\n"
344 " BINARY(==)\n"
345 " IDENTIFIER(OS)\n"
346 " LITERAL(\"mac\")\n"
347 " BLOCK\n"
348 " BINARY(+=)\n"
349 " IDENTIFIER(sources)\n"
350 " LITERAL(\"bar.cc\")\n"
351 " CONDITION\n"
352 " BINARY(==)\n"
353 " IDENTIFIER(OS)\n"
354 " LITERAL(\"win\")\n"
355 " BLOCK\n"
356 " BINARY(-=)\n"
357 " IDENTIFIER(sources)\n"
358 " LIST\n"
359 " LITERAL(\"asd.cc\")\n"
360 " LITERAL(\"foo.cc\")\n"
361 " BLOCK\n"
362 " BINARY(+=)\n"
363 " IDENTIFIER(dependencies)\n"
364 " LIST\n"
365 " LITERAL(\"bar.cc\")\n";
366 DoParserPrintTest(input, expected);
369 TEST(Parser, UnterminatedBlock) {
370 DoParserErrorTest("stuff() {", 1, 9);
373 TEST(Parser, BadlyTerminatedNumber) {
374 DoParserErrorTest("1234z", 1, 5);
377 TEST(Parser, NewlinesInUnusualPlaces) {
378 DoParserPrintTest(
379 "if\n"
380 "(\n"
381 "a\n"
382 ")\n"
383 "{\n"
384 "}\n",
385 "BLOCK\n"
386 " CONDITION\n"
387 " IDENTIFIER(a)\n"
388 " BLOCK\n");
391 TEST(Parser, NewlinesInUnusualPlaces2) {
392 DoParserPrintTest(
393 "a\n=\n2\n",
394 "BLOCK\n"
395 " BINARY(=)\n"
396 " IDENTIFIER(a)\n"
397 " LITERAL(2)\n");
398 DoParserPrintTest(
399 "x =\ny if\n(1\n) {}",
400 "BLOCK\n"
401 " BINARY(=)\n"
402 " IDENTIFIER(x)\n"
403 " IDENTIFIER(y)\n"
404 " CONDITION\n"
405 " LITERAL(1)\n"
406 " BLOCK\n");
407 DoParserPrintTest(
408 "x = 3\n+2",
409 "BLOCK\n"
410 " BINARY(=)\n"
411 " IDENTIFIER(x)\n"
412 " BINARY(+)\n"
413 " LITERAL(3)\n"
414 " LITERAL(2)\n"
418 TEST(Parser, NewlineBeforeSubscript) {
419 const char* input = "a = b[1]";
420 const char* input_with_newline = "a = b\n[1]";
421 const char* expected =
422 "BLOCK\n"
423 " BINARY(=)\n"
424 " IDENTIFIER(a)\n"
425 " ACCESSOR\n"
426 " b\n"
427 " LITERAL(1)\n";
428 DoParserPrintTest(
429 input,
430 expected);
431 DoParserPrintTest(
432 input_with_newline,
433 expected);
436 TEST(Parser, SequenceOfExpressions) {
437 DoParserPrintTest(
438 "a = 1 b = 2",
439 "BLOCK\n"
440 " BINARY(=)\n"
441 " IDENTIFIER(a)\n"
442 " LITERAL(1)\n"
443 " BINARY(=)\n"
444 " IDENTIFIER(b)\n"
445 " LITERAL(2)\n");
448 TEST(Parser, BlockAfterFunction) {
449 const char* input = "func(\"stuff\") {\n}";
450 // TODO(scottmg): Do we really want these to mean different things?
451 const char* input_with_newline = "func(\"stuff\")\n{\n}";
452 const char* expected =
453 "BLOCK\n"
454 " FUNCTION(func)\n"
455 " LIST\n"
456 " LITERAL(\"stuff\")\n"
457 " BLOCK\n";
458 DoParserPrintTest(input, expected);
459 DoParserPrintTest(input_with_newline, expected);
462 TEST(Parser, LongExpression) {
463 const char* input = "a = b + c && d || e";
464 const char* expected =
465 "BLOCK\n"
466 " BINARY(=)\n"
467 " IDENTIFIER(a)\n"
468 " BINARY(||)\n"
469 " BINARY(&&)\n"
470 " BINARY(+)\n"
471 " IDENTIFIER(b)\n"
472 " IDENTIFIER(c)\n"
473 " IDENTIFIER(d)\n"
474 " IDENTIFIER(e)\n";
475 DoParserPrintTest(input, expected);
478 TEST(Parser, CommentsStandalone) {
479 const char* input =
480 "# Toplevel comment.\n"
481 "\n"
482 "executable(\"wee\") {}\n";
483 const char* expected =
484 "BLOCK\n"
485 " BLOCK_COMMENT(# Toplevel comment.)\n"
486 " FUNCTION(executable)\n"
487 " LIST\n"
488 " LITERAL(\"wee\")\n"
489 " BLOCK\n";
490 DoParserPrintTest(input, expected);
493 TEST(Parser, CommentsStandaloneEof) {
494 const char* input =
495 "executable(\"wee\") {}\n"
496 "# EOF comment.\n";
497 const char* expected =
498 "BLOCK\n"
499 " +AFTER_COMMENT(\"# EOF comment.\")\n"
500 " FUNCTION(executable)\n"
501 " LIST\n"
502 " LITERAL(\"wee\")\n"
503 " BLOCK\n";
504 DoParserPrintTest(input, expected);
507 TEST(Parser, CommentsLineAttached) {
508 const char* input =
509 "executable(\"wee\") {\n"
510 " # Some sources.\n"
511 " sources = [\n"
512 " \"stuff.cc\",\n"
513 " \"things.cc\",\n"
514 " # This file is special or something.\n"
515 " \"another.cc\",\n"
516 " ]\n"
517 "}\n";
518 const char* expected =
519 "BLOCK\n"
520 " FUNCTION(executable)\n"
521 " LIST\n"
522 " LITERAL(\"wee\")\n"
523 " BLOCK\n"
524 " BINARY(=)\n"
525 " +BEFORE_COMMENT(\"# Some sources.\")\n"
526 " IDENTIFIER(sources)\n"
527 " LIST\n"
528 " LITERAL(\"stuff.cc\")\n"
529 " LITERAL(\"things.cc\")\n"
530 " LITERAL(\"another.cc\")\n"
531 " +BEFORE_COMMENT(\"# This file is special or something.\")\n";
532 DoParserPrintTest(input, expected);
535 TEST(Parser, CommentsSuffix) {
536 const char* input =
537 "executable(\"wee\") { # This is some stuff.\n"
538 "sources = [ \"a.cc\" # And another comment here.\n"
539 "] }";
540 const char* expected =
541 "BLOCK\n"
542 " FUNCTION(executable)\n"
543 " LIST\n"
544 " LITERAL(\"wee\")\n"
545 " END())\n"
546 " +SUFFIX_COMMENT(\"# This is some stuff.\")\n"
547 " BLOCK\n"
548 " BINARY(=)\n"
549 " IDENTIFIER(sources)\n"
550 " LIST\n"
551 " LITERAL(\"a.cc\")\n"
552 " +SUFFIX_COMMENT(\"# And another comment here.\")\n";
553 DoParserPrintTest(input, expected);
556 TEST(Parser, CommentsSuffixDifferentLine) {
557 const char* input =
558 "executable(\"wee\") {\n"
559 " sources = [ \"a\",\n"
560 " \"b\" ] # Comment\n"
561 "}\n";
562 const char* expected =
563 "BLOCK\n"
564 " FUNCTION(executable)\n"
565 " LIST\n"
566 " LITERAL(\"wee\")\n"
567 " BLOCK\n"
568 " BINARY(=)\n"
569 " IDENTIFIER(sources)\n"
570 " LIST\n"
571 " LITERAL(\"a\")\n"
572 " LITERAL(\"b\")\n"
573 " END(])\n"
574 " +SUFFIX_COMMENT(\"# Comment\")\n";
575 DoParserPrintTest(input, expected);
578 TEST(Parser, CommentsSuffixMultiple) {
579 const char* input =
580 "executable(\"wee\") {\n"
581 " sources = [\n"
582 " \"a\", # This is a comment,\n"
583 " # and some more,\n" // Note that this is aligned with above.
584 " # then the end.\n"
585 " ]\n"
586 "}\n";
587 const char* expected =
588 "BLOCK\n"
589 " FUNCTION(executable)\n"
590 " LIST\n"
591 " LITERAL(\"wee\")\n"
592 " BLOCK\n"
593 " BINARY(=)\n"
594 " IDENTIFIER(sources)\n"
595 " LIST\n"
596 " LITERAL(\"a\")\n"
597 " +SUFFIX_COMMENT(\"# This is a comment,\")\n"
598 " +SUFFIX_COMMENT(\"# and some more,\")\n"
599 " +SUFFIX_COMMENT(\"# then the end.\")\n";
600 DoParserPrintTest(input, expected);
603 TEST(Parser, CommentsConnectedInList) {
604 const char* input =
605 "defines = [\n"
606 "\n"
607 " # Connected comment.\n"
608 " \"WEE\",\n"
609 " \"BLORPY\",\n"
610 "]\n";
611 const char* expected =
612 "BLOCK\n"
613 " BINARY(=)\n"
614 " IDENTIFIER(defines)\n"
615 " LIST\n"
616 " LITERAL(\"WEE\")\n"
617 " +BEFORE_COMMENT(\"# Connected comment.\")\n"
618 " LITERAL(\"BLORPY\")\n";
619 DoParserPrintTest(input, expected);
622 TEST(Parser, CommentsAtEndOfBlock) {
623 const char* input =
624 "if (is_win) {\n"
625 " sources = [\"a.cc\"]\n"
626 " # Some comment at end.\n"
627 "}\n";
628 const char* expected =
629 "BLOCK\n"
630 " CONDITION\n"
631 " IDENTIFIER(is_win)\n"
632 " BLOCK\n"
633 " BINARY(=)\n"
634 " IDENTIFIER(sources)\n"
635 " LIST\n"
636 " LITERAL(\"a.cc\")\n"
637 " END(})\n"
638 " +BEFORE_COMMENT(\"# Some comment at end.\")\n";
639 DoParserPrintTest(input, expected);
642 // TODO(scottmg): I could be convinced this is incorrect. It's not clear to me
643 // which thing this comment is intended to be attached to.
644 TEST(Parser, CommentsEndOfBlockSingleLine) {
645 const char* input =
646 "defines = [ # EOL defines.\n"
647 "]\n";
648 const char* expected =
649 "BLOCK\n"
650 " BINARY(=)\n"
651 " IDENTIFIER(defines)\n"
652 " +SUFFIX_COMMENT(\"# EOL defines.\")\n"
653 " LIST\n";
654 DoParserPrintTest(input, expected);
657 TEST(Parser, HangingIf) {
658 DoParserErrorTest("if", 1, 1);
661 TEST(Parser, NegatingList) {
662 DoParserErrorTest("executable(\"wee\") { sources =- [ \"foo.cc\" ] }", 1, 30);
665 TEST(Parser, ConditionNoBracesIf) {
666 DoParserErrorTest(
667 "if (true)\n"
668 " foreach(foo, []) {}\n"
669 "else {\n"
670 " foreach(bar, []) {}\n"
671 "}\n",
672 2, 3);
675 TEST(Parser, ConditionNoBracesElse) {
676 DoParserErrorTest(
677 "if (true) {\n"
678 " foreach(foo, []) {}\n"
679 "} else\n"
680 " foreach(bar, []) {}\n",
681 4, 3);
684 TEST(Parser, ConditionNoBracesElseIf) {
685 DoParserErrorTest(
686 "if (true) {\n"
687 " foreach(foo, []) {}\n"
688 "} else if (true)\n"
689 " foreach(bar, []) {}\n",
690 4, 3);
693 // Disallow standalone {} for introducing new scopes. These are ambiguous with
694 // target declarations (e.g. is:
695 // foo("bar") {}
696 // a function with an associated block, or a standalone function with a
697 // freestanding block.
698 TEST(Parser, StandaloneBlock) {
699 DoParserErrorTest(
700 "if (true) {\n"
701 "}\n"
702 "{\n"
703 " assert(false)\n"
704 "}\n",
705 3, 1);