OLD | NEW |
| (Empty) |
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 | |
13 namespace { | |
14 | |
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(); | |
20 } | |
21 | |
22 bool 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 | |
31 bool 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. | |
41 bool 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. | |
53 bool 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 | |
65 void 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. | |
83 void 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 | |
101 TEST(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 | |
124 TEST(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 | |
159 TEST(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 | |
173 TEST(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 | |
188 TEST(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 | |
203 TEST(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 | |
232 TEST(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 | |
280 TEST(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 | |
297 TEST(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 | |
323 TEST(Parser, UnterminatedBlock) { | |
324 DoParserErrorTest("hello {", 1, 7); | |
325 } | |
326 | |
327 TEST(Parser, BadlyTerminatedNumber) { | |
328 DoParserErrorTest("1234z", 1, 5); | |
329 } | |
OLD | NEW |