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 |