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 } 330