1// Copyright 2014 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package parser 16 17import ( 18 "bytes" 19 "reflect" 20 "testing" 21 "text/scanner" 22) 23 24func mkpos(offset, line, column int) scanner.Position { 25 return scanner.Position{ 26 Offset: offset, 27 Line: line, 28 Column: column, 29 } 30} 31 32var validParseTestCases = []struct { 33 input string 34 defs []Definition 35 comments []*CommentGroup 36}{ 37 {` 38 foo {} 39 `, 40 []Definition{ 41 &Module{ 42 Type: "foo", 43 TypePos: mkpos(3, 2, 3), 44 Map: Map{ 45 LBracePos: mkpos(7, 2, 7), 46 RBracePos: mkpos(8, 2, 8), 47 }, 48 }, 49 }, 50 nil, 51 }, 52 53 {` 54 foo { 55 name: "abc", 56 } 57 `, 58 []Definition{ 59 &Module{ 60 Type: "foo", 61 TypePos: mkpos(3, 2, 3), 62 Map: Map{ 63 LBracePos: mkpos(7, 2, 7), 64 RBracePos: mkpos(27, 4, 3), 65 Properties: []*Property{ 66 { 67 Name: "name", 68 NamePos: mkpos(12, 3, 4), 69 ColonPos: mkpos(16, 3, 8), 70 Value: &String{ 71 LiteralPos: mkpos(18, 3, 10), 72 Value: "abc", 73 }, 74 }, 75 }, 76 }, 77 }, 78 }, 79 nil, 80 }, 81 82 {` 83 foo { 84 isGood: true, 85 } 86 `, 87 []Definition{ 88 &Module{ 89 Type: "foo", 90 TypePos: mkpos(3, 2, 3), 91 Map: Map{ 92 LBracePos: mkpos(7, 2, 7), 93 RBracePos: mkpos(28, 4, 3), 94 Properties: []*Property{ 95 { 96 Name: "isGood", 97 NamePos: mkpos(12, 3, 4), 98 ColonPos: mkpos(18, 3, 10), 99 Value: &Bool{ 100 LiteralPos: mkpos(20, 3, 12), 101 Value: true, 102 }, 103 }, 104 }, 105 }, 106 }, 107 }, 108 nil, 109 }, 110 111 {` 112 foo { 113 stuff: ["asdf", "jkl;", "qwert", 114 "uiop", "bnm,"] 115 } 116 `, 117 []Definition{ 118 &Module{ 119 Type: "foo", 120 TypePos: mkpos(3, 2, 3), 121 Map: Map{ 122 LBracePos: mkpos(7, 2, 7), 123 RBracePos: mkpos(67, 5, 3), 124 Properties: []*Property{ 125 { 126 Name: "stuff", 127 NamePos: mkpos(12, 3, 4), 128 ColonPos: mkpos(17, 3, 9), 129 Value: &List{ 130 LBracePos: mkpos(19, 3, 11), 131 RBracePos: mkpos(63, 4, 19), 132 Values: []Expression{ 133 &String{ 134 LiteralPos: mkpos(20, 3, 12), 135 Value: "asdf", 136 }, 137 &String{ 138 LiteralPos: mkpos(28, 3, 20), 139 Value: "jkl;", 140 }, 141 &String{ 142 LiteralPos: mkpos(36, 3, 28), 143 Value: "qwert", 144 }, 145 &String{ 146 LiteralPos: mkpos(49, 4, 5), 147 Value: "uiop", 148 }, 149 &String{ 150 LiteralPos: mkpos(57, 4, 13), 151 Value: "bnm,", 152 }, 153 }, 154 }, 155 }, 156 }, 157 }, 158 }, 159 }, 160 nil, 161 }, 162 163 {` 164 foo { 165 stuff: { 166 isGood: true, 167 name: "bar" 168 } 169 } 170 `, 171 []Definition{ 172 &Module{ 173 Type: "foo", 174 TypePos: mkpos(3, 2, 3), 175 Map: Map{ 176 LBracePos: mkpos(7, 2, 7), 177 RBracePos: mkpos(62, 7, 3), 178 Properties: []*Property{ 179 { 180 Name: "stuff", 181 NamePos: mkpos(12, 3, 4), 182 ColonPos: mkpos(17, 3, 9), 183 Value: &Map{ 184 LBracePos: mkpos(19, 3, 11), 185 RBracePos: mkpos(58, 6, 4), 186 Properties: []*Property{ 187 { 188 Name: "isGood", 189 NamePos: mkpos(25, 4, 5), 190 ColonPos: mkpos(31, 4, 11), 191 Value: &Bool{ 192 LiteralPos: mkpos(33, 4, 13), 193 Value: true, 194 }, 195 }, 196 { 197 Name: "name", 198 NamePos: mkpos(43, 5, 5), 199 ColonPos: mkpos(47, 5, 9), 200 Value: &String{ 201 LiteralPos: mkpos(49, 5, 11), 202 Value: "bar", 203 }, 204 }, 205 }, 206 }, 207 }, 208 }, 209 }, 210 }, 211 }, 212 nil, 213 }, 214 215 {` 216 // comment1 217 foo /* test */ { 218 // comment2 219 isGood: true, // comment3 220 } 221 `, 222 []Definition{ 223 &Module{ 224 Type: "foo", 225 TypePos: mkpos(17, 3, 3), 226 Map: Map{ 227 LBracePos: mkpos(32, 3, 18), 228 RBracePos: mkpos(81, 6, 3), 229 Properties: []*Property{ 230 { 231 Name: "isGood", 232 NamePos: mkpos(52, 5, 4), 233 ColonPos: mkpos(58, 5, 10), 234 Value: &Bool{ 235 LiteralPos: mkpos(60, 5, 12), 236 Value: true, 237 }, 238 }, 239 }, 240 }, 241 }, 242 }, 243 []*CommentGroup{ 244 { 245 Comments: []*Comment{ 246 &Comment{ 247 Comment: []string{"// comment1"}, 248 Slash: mkpos(3, 2, 3), 249 }, 250 }, 251 }, 252 { 253 Comments: []*Comment{ 254 &Comment{ 255 Comment: []string{"/* test */"}, 256 Slash: mkpos(21, 3, 7), 257 }, 258 }, 259 }, 260 { 261 Comments: []*Comment{ 262 &Comment{ 263 Comment: []string{"// comment2"}, 264 Slash: mkpos(37, 4, 4), 265 }, 266 }, 267 }, 268 { 269 Comments: []*Comment{ 270 &Comment{ 271 Comment: []string{"// comment3"}, 272 Slash: mkpos(67, 5, 19), 273 }, 274 }, 275 }, 276 }, 277 }, 278 279 {` 280 foo { 281 name: "abc", 282 } 283 284 bar { 285 name: "def", 286 } 287 `, 288 []Definition{ 289 &Module{ 290 Type: "foo", 291 TypePos: mkpos(3, 2, 3), 292 Map: Map{ 293 LBracePos: mkpos(7, 2, 7), 294 RBracePos: mkpos(27, 4, 3), 295 Properties: []*Property{ 296 { 297 Name: "name", 298 NamePos: mkpos(12, 3, 4), 299 ColonPos: mkpos(16, 3, 8), 300 Value: &String{ 301 LiteralPos: mkpos(18, 3, 10), 302 Value: "abc", 303 }, 304 }, 305 }, 306 }, 307 }, 308 &Module{ 309 Type: "bar", 310 TypePos: mkpos(32, 6, 3), 311 Map: Map{ 312 LBracePos: mkpos(36, 6, 7), 313 RBracePos: mkpos(56, 8, 3), 314 Properties: []*Property{ 315 { 316 Name: "name", 317 NamePos: mkpos(41, 7, 4), 318 ColonPos: mkpos(45, 7, 8), 319 Value: &String{ 320 LiteralPos: mkpos(47, 7, 10), 321 Value: "def", 322 }, 323 }, 324 }, 325 }, 326 }, 327 }, 328 nil, 329 }, 330 {` 331 foo = "stuff" 332 bar = foo 333 baz = foo + bar 334 boo = baz 335 boo += foo 336 `, 337 []Definition{ 338 &Assignment{ 339 Name: "foo", 340 NamePos: mkpos(3, 2, 3), 341 EqualsPos: mkpos(7, 2, 7), 342 Value: &String{ 343 LiteralPos: mkpos(9, 2, 9), 344 Value: "stuff", 345 }, 346 OrigValue: &String{ 347 LiteralPos: mkpos(9, 2, 9), 348 Value: "stuff", 349 }, 350 Assigner: "=", 351 Referenced: true, 352 }, 353 &Assignment{ 354 Name: "bar", 355 NamePos: mkpos(19, 3, 3), 356 EqualsPos: mkpos(23, 3, 7), 357 Value: &Variable{ 358 Name: "foo", 359 NamePos: mkpos(25, 3, 9), 360 Value: &String{ 361 LiteralPos: mkpos(9, 2, 9), 362 Value: "stuff", 363 }, 364 }, 365 OrigValue: &Variable{ 366 Name: "foo", 367 NamePos: mkpos(25, 3, 9), 368 Value: &String{ 369 LiteralPos: mkpos(9, 2, 9), 370 Value: "stuff", 371 }, 372 }, 373 Assigner: "=", 374 Referenced: true, 375 }, 376 &Assignment{ 377 Name: "baz", 378 NamePos: mkpos(31, 4, 3), 379 EqualsPos: mkpos(35, 4, 7), 380 Value: &Operator{ 381 OperatorPos: mkpos(41, 4, 13), 382 Operator: '+', 383 Value: &String{ 384 LiteralPos: mkpos(9, 2, 9), 385 Value: "stuffstuff", 386 }, 387 Args: [2]Expression{ 388 &Variable{ 389 Name: "foo", 390 NamePos: mkpos(37, 4, 9), 391 Value: &String{ 392 LiteralPos: mkpos(9, 2, 9), 393 Value: "stuff", 394 }, 395 }, 396 &Variable{ 397 Name: "bar", 398 NamePos: mkpos(43, 4, 15), 399 Value: &Variable{ 400 Name: "foo", 401 NamePos: mkpos(25, 3, 9), 402 Value: &String{ 403 LiteralPos: mkpos(9, 2, 9), 404 Value: "stuff", 405 }, 406 }, 407 }, 408 }, 409 }, 410 OrigValue: &Operator{ 411 OperatorPos: mkpos(41, 4, 13), 412 Operator: '+', 413 Value: &String{ 414 LiteralPos: mkpos(9, 2, 9), 415 Value: "stuffstuff", 416 }, 417 Args: [2]Expression{ 418 &Variable{ 419 Name: "foo", 420 NamePos: mkpos(37, 4, 9), 421 Value: &String{ 422 LiteralPos: mkpos(9, 2, 9), 423 Value: "stuff", 424 }, 425 }, 426 &Variable{ 427 Name: "bar", 428 NamePos: mkpos(43, 4, 15), 429 Value: &Variable{ 430 Name: "foo", 431 NamePos: mkpos(25, 3, 9), 432 Value: &String{ 433 LiteralPos: mkpos(9, 2, 9), 434 Value: "stuff", 435 }, 436 }, 437 }, 438 }, 439 }, 440 Assigner: "=", 441 Referenced: true, 442 }, 443 &Assignment{ 444 Name: "boo", 445 NamePos: mkpos(49, 5, 3), 446 EqualsPos: mkpos(53, 5, 7), 447 Value: &Operator{ 448 Args: [2]Expression{ 449 &Variable{ 450 Name: "baz", 451 NamePos: mkpos(55, 5, 9), 452 Value: &Operator{ 453 OperatorPos: mkpos(41, 4, 13), 454 Operator: '+', 455 Value: &String{ 456 LiteralPos: mkpos(9, 2, 9), 457 Value: "stuffstuff", 458 }, 459 Args: [2]Expression{ 460 &Variable{ 461 Name: "foo", 462 NamePos: mkpos(37, 4, 9), 463 Value: &String{ 464 LiteralPos: mkpos(9, 2, 9), 465 Value: "stuff", 466 }, 467 }, 468 &Variable{ 469 Name: "bar", 470 NamePos: mkpos(43, 4, 15), 471 Value: &Variable{ 472 Name: "foo", 473 NamePos: mkpos(25, 3, 9), 474 Value: &String{ 475 LiteralPos: mkpos(9, 2, 9), 476 Value: "stuff", 477 }, 478 }, 479 }, 480 }, 481 }, 482 }, 483 &Variable{ 484 Name: "foo", 485 NamePos: mkpos(68, 6, 10), 486 Value: &String{ 487 LiteralPos: mkpos(9, 2, 9), 488 Value: "stuff", 489 }, 490 }, 491 }, 492 OperatorPos: mkpos(66, 6, 8), 493 Operator: '+', 494 Value: &String{ 495 LiteralPos: mkpos(9, 2, 9), 496 Value: "stuffstuffstuff", 497 }, 498 }, 499 OrigValue: &Variable{ 500 Name: "baz", 501 NamePos: mkpos(55, 5, 9), 502 Value: &Operator{ 503 OperatorPos: mkpos(41, 4, 13), 504 Operator: '+', 505 Value: &String{ 506 LiteralPos: mkpos(9, 2, 9), 507 Value: "stuffstuff", 508 }, 509 Args: [2]Expression{ 510 &Variable{ 511 Name: "foo", 512 NamePos: mkpos(37, 4, 9), 513 Value: &String{ 514 LiteralPos: mkpos(9, 2, 9), 515 Value: "stuff", 516 }, 517 }, 518 &Variable{ 519 Name: "bar", 520 NamePos: mkpos(43, 4, 15), 521 Value: &Variable{ 522 Name: "foo", 523 NamePos: mkpos(25, 3, 9), 524 Value: &String{ 525 LiteralPos: mkpos(9, 2, 9), 526 Value: "stuff", 527 }, 528 }, 529 }, 530 }, 531 }, 532 }, 533 Assigner: "=", 534 }, 535 &Assignment{ 536 Name: "boo", 537 NamePos: mkpos(61, 6, 3), 538 EqualsPos: mkpos(66, 6, 8), 539 Value: &Variable{ 540 Name: "foo", 541 NamePos: mkpos(68, 6, 10), 542 Value: &String{ 543 LiteralPos: mkpos(9, 2, 9), 544 Value: "stuff", 545 }, 546 }, 547 OrigValue: &Variable{ 548 Name: "foo", 549 NamePos: mkpos(68, 6, 10), 550 Value: &String{ 551 LiteralPos: mkpos(9, 2, 9), 552 Value: "stuff", 553 }, 554 }, 555 Assigner: "+=", 556 }, 557 }, 558 nil, 559 }, 560 {` 561 // comment1 562 // comment2 563 564 /* comment3 565 comment4 */ 566 // comment5 567 568 /* comment6 */ /* comment7 */ // comment8 569 `, 570 nil, 571 []*CommentGroup{ 572 { 573 Comments: []*Comment{ 574 &Comment{ 575 Comment: []string{"// comment1"}, 576 Slash: mkpos(3, 2, 3), 577 }, 578 &Comment{ 579 Comment: []string{"// comment2"}, 580 Slash: mkpos(17, 3, 3), 581 }, 582 }, 583 }, 584 { 585 Comments: []*Comment{ 586 &Comment{ 587 Comment: []string{"/* comment3", " comment4 */"}, 588 Slash: mkpos(32, 5, 3), 589 }, 590 &Comment{ 591 Comment: []string{"// comment5"}, 592 Slash: mkpos(63, 7, 3), 593 }, 594 }, 595 }, 596 { 597 Comments: []*Comment{ 598 &Comment{ 599 Comment: []string{"/* comment6 */"}, 600 Slash: mkpos(78, 9, 3), 601 }, 602 &Comment{ 603 Comment: []string{"/* comment7 */"}, 604 Slash: mkpos(93, 9, 18), 605 }, 606 &Comment{ 607 Comment: []string{"// comment8"}, 608 Slash: mkpos(108, 9, 33), 609 }, 610 }, 611 }, 612 }, 613 }, 614} 615 616func TestParseValidInput(t *testing.T) { 617 for _, testCase := range validParseTestCases { 618 r := bytes.NewBufferString(testCase.input) 619 file, errs := ParseAndEval("", r, NewScope(nil)) 620 if len(errs) != 0 { 621 t.Errorf("test case: %s", testCase.input) 622 t.Errorf("unexpected errors:") 623 for _, err := range errs { 624 t.Errorf(" %s", err) 625 } 626 t.FailNow() 627 } 628 629 if len(file.Defs) == len(testCase.defs) { 630 for i := range file.Defs { 631 if !reflect.DeepEqual(file.Defs[i], testCase.defs[i]) { 632 t.Errorf("test case: %s", testCase.input) 633 t.Errorf("incorrect defintion %d:", i) 634 t.Errorf(" expected: %s", testCase.defs[i]) 635 t.Errorf(" got: %s", file.Defs[i]) 636 } 637 } 638 } else { 639 t.Errorf("test case: %s", testCase.input) 640 t.Errorf("length mismatch, expected %d definitions, got %d", 641 len(testCase.defs), len(file.Defs)) 642 } 643 644 if len(file.Comments) == len(testCase.comments) { 645 for i := range file.Comments { 646 if !reflect.DeepEqual(file.Comments[i], testCase.comments[i]) { 647 t.Errorf("test case: %s", testCase.input) 648 t.Errorf("incorrect comment %d:", i) 649 t.Errorf(" expected: %s", testCase.comments[i]) 650 t.Errorf(" got: %s", file.Comments[i]) 651 } 652 } 653 } else { 654 t.Errorf("test case: %s", testCase.input) 655 t.Errorf("length mismatch, expected %d comments, got %d", 656 len(testCase.comments), len(file.Comments)) 657 } 658 } 659} 660 661// TODO: Test error strings 662