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 "strconv" 21 "strings" 22 "testing" 23 "text/scanner" 24) 25 26func mkpos(offset, line, column int) scanner.Position { 27 return scanner.Position{ 28 Offset: offset, 29 Line: line, 30 Column: column, 31 } 32} 33 34var validParseTestCases = []struct { 35 input string 36 defs []Definition 37 comments []*CommentGroup 38}{ 39 {` 40 foo {} 41 `, 42 []Definition{ 43 &Module{ 44 Type: "foo", 45 TypePos: mkpos(3, 2, 3), 46 Map: Map{ 47 LBracePos: mkpos(7, 2, 7), 48 RBracePos: mkpos(8, 2, 8), 49 }, 50 }, 51 }, 52 nil, 53 }, 54 55 {` 56 foo { 57 name: "abc", 58 } 59 `, 60 []Definition{ 61 &Module{ 62 Type: "foo", 63 TypePos: mkpos(3, 2, 3), 64 Map: Map{ 65 LBracePos: mkpos(7, 2, 7), 66 RBracePos: mkpos(27, 4, 3), 67 Properties: []*Property{ 68 { 69 Name: "name", 70 NamePos: mkpos(12, 3, 4), 71 ColonPos: mkpos(16, 3, 8), 72 Value: &String{ 73 LiteralPos: mkpos(18, 3, 10), 74 Value: "abc", 75 }, 76 }, 77 }, 78 }, 79 }, 80 }, 81 nil, 82 }, 83 84 {` 85 foo { 86 isGood: true, 87 } 88 `, 89 []Definition{ 90 &Module{ 91 Type: "foo", 92 TypePos: mkpos(3, 2, 3), 93 Map: Map{ 94 LBracePos: mkpos(7, 2, 7), 95 RBracePos: mkpos(28, 4, 3), 96 Properties: []*Property{ 97 { 98 Name: "isGood", 99 NamePos: mkpos(12, 3, 4), 100 ColonPos: mkpos(18, 3, 10), 101 Value: &Bool{ 102 LiteralPos: mkpos(20, 3, 12), 103 Value: true, 104 Token: "true", 105 }, 106 }, 107 }, 108 }, 109 }, 110 }, 111 nil, 112 }, 113 114 {` 115 foo { 116 num: 4, 117 } 118 `, 119 []Definition{ 120 &Module{ 121 Type: "foo", 122 TypePos: mkpos(3, 2, 3), 123 Map: Map{ 124 LBracePos: mkpos(7, 2, 7), 125 RBracePos: mkpos(22, 4, 3), 126 Properties: []*Property{ 127 { 128 Name: "num", 129 NamePos: mkpos(12, 3, 4), 130 ColonPos: mkpos(15, 3, 7), 131 Value: &Int64{ 132 LiteralPos: mkpos(17, 3, 9), 133 Value: 4, 134 Token: "4", 135 }, 136 }, 137 }, 138 }, 139 }, 140 }, 141 nil, 142 }, 143 144 {` 145 foo { 146 stuff: ["asdf", "jkl;", "qwert", 147 "uiop", ` + "`bnm,\n`" + 148 `] 149 } 150 `, 151 []Definition{ 152 &Module{ 153 Type: "foo", 154 TypePos: mkpos(3, 2, 3), 155 Map: Map{ 156 LBracePos: mkpos(7, 2, 7), 157 RBracePos: mkpos(68, 6, 3), 158 Properties: []*Property{ 159 { 160 Name: "stuff", 161 NamePos: mkpos(12, 3, 4), 162 ColonPos: mkpos(17, 3, 9), 163 Value: &List{ 164 LBracePos: mkpos(19, 3, 11), 165 RBracePos: mkpos(64, 5, 2), 166 Values: []Expression{ 167 &String{ 168 LiteralPos: mkpos(20, 3, 12), 169 Value: "asdf", 170 }, 171 &String{ 172 LiteralPos: mkpos(28, 3, 20), 173 Value: "jkl;", 174 }, 175 &String{ 176 LiteralPos: mkpos(36, 3, 28), 177 Value: "qwert", 178 }, 179 &String{ 180 LiteralPos: mkpos(49, 4, 5), 181 Value: "uiop", 182 }, 183 &String{ 184 LiteralPos: mkpos(57, 4, 13), 185 Value: "bnm,\n", 186 }, 187 }, 188 }, 189 }, 190 }, 191 }, 192 }, 193 }, 194 nil, 195 }, 196 197 { 198 ` 199 foo { 200 list_of_maps: [ 201 { 202 var: true, 203 name: "a", 204 }, 205 { 206 var: false, 207 name: "b", 208 }, 209 ], 210 } 211`, 212 []Definition{ 213 &Module{ 214 Type: "foo", 215 TypePos: mkpos(3, 2, 3), 216 Map: Map{ 217 LBracePos: mkpos(7, 2, 7), 218 RBracePos: mkpos(127, 13, 3), 219 Properties: []*Property{ 220 { 221 Name: "list_of_maps", 222 NamePos: mkpos(12, 3, 4), 223 ColonPos: mkpos(24, 3, 16), 224 Value: &List{ 225 LBracePos: mkpos(26, 3, 18), 226 RBracePos: mkpos(122, 12, 4), 227 Values: []Expression{ 228 &Map{ 229 LBracePos: mkpos(32, 4, 5), 230 RBracePos: mkpos(70, 7, 5), 231 Properties: []*Property{ 232 { 233 Name: "var", 234 NamePos: mkpos(39, 5, 6), 235 ColonPos: mkpos(42, 5, 9), 236 Value: &Bool{ 237 LiteralPos: mkpos(44, 5, 11), 238 Value: true, 239 Token: "true", 240 }, 241 }, 242 { 243 Name: "name", 244 NamePos: mkpos(55, 6, 6), 245 ColonPos: mkpos(59, 6, 10), 246 Value: &String{ 247 LiteralPos: mkpos(61, 6, 12), 248 Value: "a", 249 }, 250 }, 251 }, 252 }, 253 &Map{ 254 LBracePos: mkpos(77, 8, 5), 255 RBracePos: mkpos(116, 11, 5), 256 Properties: []*Property{ 257 { 258 Name: "var", 259 NamePos: mkpos(84, 9, 6), 260 ColonPos: mkpos(87, 9, 9), 261 Value: &Bool{ 262 LiteralPos: mkpos(89, 9, 11), 263 Value: false, 264 Token: "false", 265 }, 266 }, 267 { 268 Name: "name", 269 NamePos: mkpos(101, 10, 6), 270 ColonPos: mkpos(105, 10, 10), 271 Value: &String{ 272 LiteralPos: mkpos(107, 10, 12), 273 Value: "b", 274 }, 275 }, 276 }, 277 }, 278 }, 279 }, 280 }, 281 }, 282 }, 283 }, 284 }, 285 nil, 286 }, 287 { 288 ` 289 foo { 290 list_of_lists: [ 291 [ "a", "b" ], 292 [ "c", "d" ] 293 ], 294 } 295`, 296 []Definition{ 297 &Module{ 298 Type: "foo", 299 TypePos: mkpos(3, 2, 3), 300 Map: Map{ 301 LBracePos: mkpos(7, 2, 7), 302 RBracePos: mkpos(72, 7, 3), 303 Properties: []*Property{ 304 { 305 Name: "list_of_lists", 306 NamePos: mkpos(12, 3, 4), 307 ColonPos: mkpos(25, 3, 17), 308 Value: &List{ 309 LBracePos: mkpos(27, 3, 19), 310 RBracePos: mkpos(67, 6, 4), 311 Values: []Expression{ 312 &List{ 313 LBracePos: mkpos(33, 4, 5), 314 RBracePos: mkpos(44, 4, 16), 315 Values: []Expression{ 316 &String{ 317 LiteralPos: mkpos(35, 4, 7), 318 Value: "a", 319 }, 320 &String{ 321 LiteralPos: mkpos(40, 4, 12), 322 Value: "b", 323 }, 324 }, 325 }, 326 &List{ 327 LBracePos: mkpos(51, 5, 5), 328 RBracePos: mkpos(62, 5, 16), 329 Values: []Expression{ 330 &String{ 331 LiteralPos: mkpos(53, 5, 7), 332 Value: "c", 333 }, 334 &String{ 335 LiteralPos: mkpos(58, 5, 12), 336 Value: "d", 337 }, 338 }, 339 }, 340 }, 341 }, 342 }, 343 }, 344 }, 345 }, 346 }, 347 nil, 348 }, 349 {` 350 foo { 351 stuff: { 352 isGood: true, 353 name: "bar", 354 num: 36, 355 } 356 } 357 `, 358 []Definition{ 359 &Module{ 360 Type: "foo", 361 TypePos: mkpos(3, 2, 3), 362 Map: Map{ 363 LBracePos: mkpos(7, 2, 7), 364 RBracePos: mkpos(76, 8, 3), 365 Properties: []*Property{ 366 { 367 Name: "stuff", 368 NamePos: mkpos(12, 3, 4), 369 ColonPos: mkpos(17, 3, 9), 370 Value: &Map{ 371 LBracePos: mkpos(19, 3, 11), 372 RBracePos: mkpos(72, 7, 4), 373 Properties: []*Property{ 374 { 375 Name: "isGood", 376 NamePos: mkpos(25, 4, 5), 377 ColonPos: mkpos(31, 4, 11), 378 Value: &Bool{ 379 LiteralPos: mkpos(33, 4, 13), 380 Value: true, 381 Token: "true", 382 }, 383 }, 384 { 385 Name: "name", 386 NamePos: mkpos(43, 5, 5), 387 ColonPos: mkpos(47, 5, 9), 388 Value: &String{ 389 LiteralPos: mkpos(49, 5, 11), 390 Value: "bar", 391 }, 392 }, 393 { 394 Name: "num", 395 NamePos: mkpos(60, 6, 5), 396 ColonPos: mkpos(63, 6, 8), 397 Value: &Int64{ 398 LiteralPos: mkpos(65, 6, 10), 399 Value: 36, 400 Token: "36", 401 }, 402 }, 403 }, 404 }, 405 }, 406 }, 407 }, 408 }, 409 }, 410 nil, 411 }, 412 413 {` 414 // comment1 415 foo /* test */ { 416 // comment2 417 isGood: true, // comment3 418 } 419 `, 420 []Definition{ 421 &Module{ 422 Type: "foo", 423 TypePos: mkpos(17, 3, 3), 424 Map: Map{ 425 LBracePos: mkpos(32, 3, 18), 426 RBracePos: mkpos(81, 6, 3), 427 Properties: []*Property{ 428 { 429 Name: "isGood", 430 NamePos: mkpos(52, 5, 4), 431 ColonPos: mkpos(58, 5, 10), 432 Value: &Bool{ 433 LiteralPos: mkpos(60, 5, 12), 434 Value: true, 435 Token: "true", 436 }, 437 }, 438 }, 439 }, 440 }, 441 }, 442 []*CommentGroup{ 443 { 444 Comments: []*Comment{ 445 &Comment{ 446 Comment: []string{"// comment1"}, 447 Slash: mkpos(3, 2, 3), 448 }, 449 }, 450 }, 451 { 452 Comments: []*Comment{ 453 &Comment{ 454 Comment: []string{"/* test */"}, 455 Slash: mkpos(21, 3, 7), 456 }, 457 }, 458 }, 459 { 460 Comments: []*Comment{ 461 &Comment{ 462 Comment: []string{"// comment2"}, 463 Slash: mkpos(37, 4, 4), 464 }, 465 }, 466 }, 467 { 468 Comments: []*Comment{ 469 &Comment{ 470 Comment: []string{"// comment3"}, 471 Slash: mkpos(67, 5, 19), 472 }, 473 }, 474 }, 475 }, 476 }, 477 478 {` 479 foo { 480 name: "abc", 481 num: 4, 482 } 483 484 bar { 485 name: "def", 486 num: -5, 487 } 488 `, 489 []Definition{ 490 &Module{ 491 Type: "foo", 492 TypePos: mkpos(3, 2, 3), 493 Map: Map{ 494 LBracePos: mkpos(7, 2, 7), 495 RBracePos: mkpos(38, 5, 3), 496 Properties: []*Property{ 497 { 498 Name: "name", 499 NamePos: mkpos(12, 3, 4), 500 ColonPos: mkpos(16, 3, 8), 501 Value: &String{ 502 LiteralPos: mkpos(18, 3, 10), 503 Value: "abc", 504 }, 505 }, 506 { 507 Name: "num", 508 NamePos: mkpos(28, 4, 4), 509 ColonPos: mkpos(31, 4, 7), 510 Value: &Int64{ 511 LiteralPos: mkpos(33, 4, 9), 512 Value: 4, 513 Token: "4", 514 }, 515 }, 516 }, 517 }, 518 }, 519 &Module{ 520 Type: "bar", 521 TypePos: mkpos(43, 7, 3), 522 Map: Map{ 523 LBracePos: mkpos(47, 7, 7), 524 RBracePos: mkpos(79, 10, 3), 525 Properties: []*Property{ 526 { 527 Name: "name", 528 NamePos: mkpos(52, 8, 4), 529 ColonPos: mkpos(56, 8, 8), 530 Value: &String{ 531 LiteralPos: mkpos(58, 8, 10), 532 Value: "def", 533 }, 534 }, 535 { 536 Name: "num", 537 NamePos: mkpos(68, 9, 4), 538 ColonPos: mkpos(71, 9, 7), 539 Value: &Int64{ 540 LiteralPos: mkpos(73, 9, 9), 541 Value: -5, 542 Token: "-5", 543 }, 544 }, 545 }, 546 }, 547 }, 548 }, 549 nil, 550 }, 551 552 {` 553 foo = "stuff" 554 bar = foo 555 baz = foo + bar 556 boo = baz 557 boo += foo 558 `, 559 []Definition{ 560 &Assignment{ 561 Name: "foo", 562 NamePos: mkpos(3, 2, 3), 563 EqualsPos: mkpos(7, 2, 7), 564 Value: &String{ 565 LiteralPos: mkpos(9, 2, 9), 566 Value: "stuff", 567 }, 568 OrigValue: &String{ 569 LiteralPos: mkpos(9, 2, 9), 570 Value: "stuff", 571 }, 572 Assigner: "=", 573 Referenced: true, 574 }, 575 &Assignment{ 576 Name: "bar", 577 NamePos: mkpos(19, 3, 3), 578 EqualsPos: mkpos(23, 3, 7), 579 Value: &Variable{ 580 Name: "foo", 581 NamePos: mkpos(25, 3, 9), 582 Value: &String{ 583 LiteralPos: mkpos(9, 2, 9), 584 Value: "stuff", 585 }, 586 }, 587 OrigValue: &Variable{ 588 Name: "foo", 589 NamePos: mkpos(25, 3, 9), 590 Value: &String{ 591 LiteralPos: mkpos(9, 2, 9), 592 Value: "stuff", 593 }, 594 }, 595 Assigner: "=", 596 Referenced: true, 597 }, 598 &Assignment{ 599 Name: "baz", 600 NamePos: mkpos(31, 4, 3), 601 EqualsPos: mkpos(35, 4, 7), 602 Value: &Operator{ 603 OperatorPos: mkpos(41, 4, 13), 604 Operator: '+', 605 Value: &String{ 606 LiteralPos: mkpos(9, 2, 9), 607 Value: "stuffstuff", 608 }, 609 Args: [2]Expression{ 610 &Variable{ 611 Name: "foo", 612 NamePos: mkpos(37, 4, 9), 613 Value: &String{ 614 LiteralPos: mkpos(9, 2, 9), 615 Value: "stuff", 616 }, 617 }, 618 &Variable{ 619 Name: "bar", 620 NamePos: mkpos(43, 4, 15), 621 Value: &Variable{ 622 Name: "foo", 623 NamePos: mkpos(25, 3, 9), 624 Value: &String{ 625 LiteralPos: mkpos(9, 2, 9), 626 Value: "stuff", 627 }, 628 }, 629 }, 630 }, 631 }, 632 OrigValue: &Operator{ 633 OperatorPos: mkpos(41, 4, 13), 634 Operator: '+', 635 Value: &String{ 636 LiteralPos: mkpos(9, 2, 9), 637 Value: "stuffstuff", 638 }, 639 Args: [2]Expression{ 640 &Variable{ 641 Name: "foo", 642 NamePos: mkpos(37, 4, 9), 643 Value: &String{ 644 LiteralPos: mkpos(9, 2, 9), 645 Value: "stuff", 646 }, 647 }, 648 &Variable{ 649 Name: "bar", 650 NamePos: mkpos(43, 4, 15), 651 Value: &Variable{ 652 Name: "foo", 653 NamePos: mkpos(25, 3, 9), 654 Value: &String{ 655 LiteralPos: mkpos(9, 2, 9), 656 Value: "stuff", 657 }, 658 }, 659 }, 660 }, 661 }, 662 Assigner: "=", 663 Referenced: true, 664 }, 665 &Assignment{ 666 Name: "boo", 667 NamePos: mkpos(49, 5, 3), 668 EqualsPos: mkpos(53, 5, 7), 669 Value: &Operator{ 670 Args: [2]Expression{ 671 &Variable{ 672 Name: "baz", 673 NamePos: mkpos(55, 5, 9), 674 Value: &Operator{ 675 OperatorPos: mkpos(41, 4, 13), 676 Operator: '+', 677 Value: &String{ 678 LiteralPos: mkpos(9, 2, 9), 679 Value: "stuffstuff", 680 }, 681 Args: [2]Expression{ 682 &Variable{ 683 Name: "foo", 684 NamePos: mkpos(37, 4, 9), 685 Value: &String{ 686 LiteralPos: mkpos(9, 2, 9), 687 Value: "stuff", 688 }, 689 }, 690 &Variable{ 691 Name: "bar", 692 NamePos: mkpos(43, 4, 15), 693 Value: &Variable{ 694 Name: "foo", 695 NamePos: mkpos(25, 3, 9), 696 Value: &String{ 697 LiteralPos: mkpos(9, 2, 9), 698 Value: "stuff", 699 }, 700 }, 701 }, 702 }, 703 }, 704 }, 705 &Variable{ 706 Name: "foo", 707 NamePos: mkpos(68, 6, 10), 708 Value: &String{ 709 LiteralPos: mkpos(9, 2, 9), 710 Value: "stuff", 711 }, 712 }, 713 }, 714 OperatorPos: mkpos(66, 6, 8), 715 Operator: '+', 716 Value: &String{ 717 LiteralPos: mkpos(9, 2, 9), 718 Value: "stuffstuffstuff", 719 }, 720 }, 721 OrigValue: &Variable{ 722 Name: "baz", 723 NamePos: mkpos(55, 5, 9), 724 Value: &Operator{ 725 OperatorPos: mkpos(41, 4, 13), 726 Operator: '+', 727 Value: &String{ 728 LiteralPos: mkpos(9, 2, 9), 729 Value: "stuffstuff", 730 }, 731 Args: [2]Expression{ 732 &Variable{ 733 Name: "foo", 734 NamePos: mkpos(37, 4, 9), 735 Value: &String{ 736 LiteralPos: mkpos(9, 2, 9), 737 Value: "stuff", 738 }, 739 }, 740 &Variable{ 741 Name: "bar", 742 NamePos: mkpos(43, 4, 15), 743 Value: &Variable{ 744 Name: "foo", 745 NamePos: mkpos(25, 3, 9), 746 Value: &String{ 747 LiteralPos: mkpos(9, 2, 9), 748 Value: "stuff", 749 }, 750 }, 751 }, 752 }, 753 }, 754 }, 755 Assigner: "=", 756 }, 757 &Assignment{ 758 Name: "boo", 759 NamePos: mkpos(61, 6, 3), 760 EqualsPos: mkpos(66, 6, 8), 761 Value: &Variable{ 762 Name: "foo", 763 NamePos: mkpos(68, 6, 10), 764 Value: &String{ 765 LiteralPos: mkpos(9, 2, 9), 766 Value: "stuff", 767 }, 768 }, 769 OrigValue: &Variable{ 770 Name: "foo", 771 NamePos: mkpos(68, 6, 10), 772 Value: &String{ 773 LiteralPos: mkpos(9, 2, 9), 774 Value: "stuff", 775 }, 776 }, 777 Assigner: "+=", 778 }, 779 }, 780 nil, 781 }, 782 783 {` 784 baz = -4 + -5 + 6 785 `, 786 []Definition{ 787 &Assignment{ 788 Name: "baz", 789 NamePos: mkpos(3, 2, 3), 790 EqualsPos: mkpos(7, 2, 7), 791 Value: &Operator{ 792 OperatorPos: mkpos(12, 2, 12), 793 Operator: '+', 794 Value: &Int64{ 795 LiteralPos: mkpos(9, 2, 9), 796 Value: -3, 797 }, 798 Args: [2]Expression{ 799 &Int64{ 800 LiteralPos: mkpos(9, 2, 9), 801 Value: -4, 802 Token: "-4", 803 }, 804 &Operator{ 805 OperatorPos: mkpos(17, 2, 17), 806 Operator: '+', 807 Value: &Int64{ 808 LiteralPos: mkpos(14, 2, 14), 809 Value: 1, 810 }, 811 Args: [2]Expression{ 812 &Int64{ 813 LiteralPos: mkpos(14, 2, 14), 814 Value: -5, 815 Token: "-5", 816 }, 817 &Int64{ 818 LiteralPos: mkpos(19, 2, 19), 819 Value: 6, 820 Token: "6", 821 }, 822 }, 823 }, 824 }, 825 }, 826 OrigValue: &Operator{ 827 OperatorPos: mkpos(12, 2, 12), 828 Operator: '+', 829 Value: &Int64{ 830 LiteralPos: mkpos(9, 2, 9), 831 Value: -3, 832 }, 833 Args: [2]Expression{ 834 &Int64{ 835 LiteralPos: mkpos(9, 2, 9), 836 Value: -4, 837 Token: "-4", 838 }, 839 &Operator{ 840 OperatorPos: mkpos(17, 2, 17), 841 Operator: '+', 842 Value: &Int64{ 843 LiteralPos: mkpos(14, 2, 14), 844 Value: 1, 845 }, 846 Args: [2]Expression{ 847 &Int64{ 848 LiteralPos: mkpos(14, 2, 14), 849 Value: -5, 850 Token: "-5", 851 }, 852 &Int64{ 853 LiteralPos: mkpos(19, 2, 19), 854 Value: 6, 855 Token: "6", 856 }, 857 }, 858 }, 859 }, 860 }, 861 Assigner: "=", 862 Referenced: false, 863 }, 864 }, 865 nil, 866 }, 867 868 {` 869 foo = 1000000 870 bar = foo 871 baz = foo + bar 872 boo = baz 873 boo += foo 874 `, 875 []Definition{ 876 &Assignment{ 877 Name: "foo", 878 NamePos: mkpos(3, 2, 3), 879 EqualsPos: mkpos(7, 2, 7), 880 Value: &Int64{ 881 LiteralPos: mkpos(9, 2, 9), 882 Value: 1000000, 883 Token: "1000000", 884 }, 885 OrigValue: &Int64{ 886 LiteralPos: mkpos(9, 2, 9), 887 Value: 1000000, 888 Token: "1000000", 889 }, 890 Assigner: "=", 891 Referenced: true, 892 }, 893 &Assignment{ 894 Name: "bar", 895 NamePos: mkpos(19, 3, 3), 896 EqualsPos: mkpos(23, 3, 7), 897 Value: &Variable{ 898 Name: "foo", 899 NamePos: mkpos(25, 3, 9), 900 Value: &Int64{ 901 LiteralPos: mkpos(9, 2, 9), 902 Value: 1000000, 903 Token: "1000000", 904 }, 905 }, 906 OrigValue: &Variable{ 907 Name: "foo", 908 NamePos: mkpos(25, 3, 9), 909 Value: &Int64{ 910 LiteralPos: mkpos(9, 2, 9), 911 Value: 1000000, 912 Token: "1000000", 913 }, 914 }, 915 Assigner: "=", 916 Referenced: true, 917 }, 918 &Assignment{ 919 Name: "baz", 920 NamePos: mkpos(31, 4, 3), 921 EqualsPos: mkpos(35, 4, 7), 922 Value: &Operator{ 923 OperatorPos: mkpos(41, 4, 13), 924 Operator: '+', 925 Value: &Int64{ 926 LiteralPos: mkpos(9, 2, 9), 927 Value: 2000000, 928 }, 929 Args: [2]Expression{ 930 &Variable{ 931 Name: "foo", 932 NamePos: mkpos(37, 4, 9), 933 Value: &Int64{ 934 LiteralPos: mkpos(9, 2, 9), 935 Value: 1000000, 936 Token: "1000000", 937 }, 938 }, 939 &Variable{ 940 Name: "bar", 941 NamePos: mkpos(43, 4, 15), 942 Value: &Variable{ 943 Name: "foo", 944 NamePos: mkpos(25, 3, 9), 945 Value: &Int64{ 946 LiteralPos: mkpos(9, 2, 9), 947 Value: 1000000, 948 Token: "1000000", 949 }, 950 }, 951 }, 952 }, 953 }, 954 OrigValue: &Operator{ 955 OperatorPos: mkpos(41, 4, 13), 956 Operator: '+', 957 Value: &Int64{ 958 LiteralPos: mkpos(9, 2, 9), 959 Value: 2000000, 960 }, 961 Args: [2]Expression{ 962 &Variable{ 963 Name: "foo", 964 NamePos: mkpos(37, 4, 9), 965 Value: &Int64{ 966 LiteralPos: mkpos(9, 2, 9), 967 Value: 1000000, 968 Token: "1000000", 969 }, 970 }, 971 &Variable{ 972 Name: "bar", 973 NamePos: mkpos(43, 4, 15), 974 Value: &Variable{ 975 Name: "foo", 976 NamePos: mkpos(25, 3, 9), 977 Value: &Int64{ 978 LiteralPos: mkpos(9, 2, 9), 979 Value: 1000000, 980 Token: "1000000", 981 }, 982 }, 983 }, 984 }, 985 }, 986 Assigner: "=", 987 Referenced: true, 988 }, 989 &Assignment{ 990 Name: "boo", 991 NamePos: mkpos(49, 5, 3), 992 EqualsPos: mkpos(53, 5, 7), 993 Value: &Operator{ 994 Args: [2]Expression{ 995 &Variable{ 996 Name: "baz", 997 NamePos: mkpos(55, 5, 9), 998 Value: &Operator{ 999 OperatorPos: mkpos(41, 4, 13), 1000 Operator: '+', 1001 Value: &Int64{ 1002 LiteralPos: mkpos(9, 2, 9), 1003 Value: 2000000, 1004 }, 1005 Args: [2]Expression{ 1006 &Variable{ 1007 Name: "foo", 1008 NamePos: mkpos(37, 4, 9), 1009 Value: &Int64{ 1010 LiteralPos: mkpos(9, 2, 9), 1011 Value: 1000000, 1012 Token: "1000000", 1013 }, 1014 }, 1015 &Variable{ 1016 Name: "bar", 1017 NamePos: mkpos(43, 4, 15), 1018 Value: &Variable{ 1019 Name: "foo", 1020 NamePos: mkpos(25, 3, 9), 1021 Value: &Int64{ 1022 LiteralPos: mkpos(9, 2, 9), 1023 Value: 1000000, 1024 Token: "1000000", 1025 }, 1026 }, 1027 }, 1028 }, 1029 }, 1030 }, 1031 &Variable{ 1032 Name: "foo", 1033 NamePos: mkpos(68, 6, 10), 1034 Value: &Int64{ 1035 LiteralPos: mkpos(9, 2, 9), 1036 Value: 1000000, 1037 Token: "1000000", 1038 }, 1039 }, 1040 }, 1041 OperatorPos: mkpos(66, 6, 8), 1042 Operator: '+', 1043 Value: &Int64{ 1044 LiteralPos: mkpos(9, 2, 9), 1045 Value: 3000000, 1046 }, 1047 }, 1048 OrigValue: &Variable{ 1049 Name: "baz", 1050 NamePos: mkpos(55, 5, 9), 1051 Value: &Operator{ 1052 OperatorPos: mkpos(41, 4, 13), 1053 Operator: '+', 1054 Value: &Int64{ 1055 LiteralPos: mkpos(9, 2, 9), 1056 Value: 2000000, 1057 }, 1058 Args: [2]Expression{ 1059 &Variable{ 1060 Name: "foo", 1061 NamePos: mkpos(37, 4, 9), 1062 Value: &Int64{ 1063 LiteralPos: mkpos(9, 2, 9), 1064 Value: 1000000, 1065 Token: "1000000", 1066 }, 1067 }, 1068 &Variable{ 1069 Name: "bar", 1070 NamePos: mkpos(43, 4, 15), 1071 Value: &Variable{ 1072 Name: "foo", 1073 NamePos: mkpos(25, 3, 9), 1074 Value: &Int64{ 1075 LiteralPos: mkpos(9, 2, 9), 1076 Value: 1000000, 1077 Token: "1000000", 1078 }, 1079 }, 1080 }, 1081 }, 1082 }, 1083 }, 1084 Assigner: "=", 1085 }, 1086 &Assignment{ 1087 Name: "boo", 1088 NamePos: mkpos(61, 6, 3), 1089 EqualsPos: mkpos(66, 6, 8), 1090 Value: &Variable{ 1091 Name: "foo", 1092 NamePos: mkpos(68, 6, 10), 1093 Value: &Int64{ 1094 LiteralPos: mkpos(9, 2, 9), 1095 Value: 1000000, 1096 Token: "1000000", 1097 }, 1098 }, 1099 OrigValue: &Variable{ 1100 Name: "foo", 1101 NamePos: mkpos(68, 6, 10), 1102 Value: &Int64{ 1103 LiteralPos: mkpos(9, 2, 9), 1104 Value: 1000000, 1105 Token: "1000000", 1106 }, 1107 }, 1108 Assigner: "+=", 1109 }, 1110 }, 1111 nil, 1112 }, 1113 1114 {` 1115 // comment1 1116 // comment2 1117 1118 /* comment3 1119 comment4 */ 1120 // comment5 1121 1122 /* comment6 */ /* comment7 */ // comment8 1123 `, 1124 nil, 1125 []*CommentGroup{ 1126 { 1127 Comments: []*Comment{ 1128 &Comment{ 1129 Comment: []string{"// comment1"}, 1130 Slash: mkpos(3, 2, 3), 1131 }, 1132 &Comment{ 1133 Comment: []string{"// comment2"}, 1134 Slash: mkpos(17, 3, 3), 1135 }, 1136 }, 1137 }, 1138 { 1139 Comments: []*Comment{ 1140 &Comment{ 1141 Comment: []string{"/* comment3", " comment4 */"}, 1142 Slash: mkpos(32, 5, 3), 1143 }, 1144 &Comment{ 1145 Comment: []string{"// comment5"}, 1146 Slash: mkpos(63, 7, 3), 1147 }, 1148 }, 1149 }, 1150 { 1151 Comments: []*Comment{ 1152 &Comment{ 1153 Comment: []string{"/* comment6 */"}, 1154 Slash: mkpos(78, 9, 3), 1155 }, 1156 &Comment{ 1157 Comment: []string{"/* comment7 */"}, 1158 Slash: mkpos(93, 9, 18), 1159 }, 1160 &Comment{ 1161 Comment: []string{"// comment8"}, 1162 Slash: mkpos(108, 9, 33), 1163 }, 1164 }, 1165 }, 1166 }, 1167 }, 1168} 1169 1170func TestParseValidInput(t *testing.T) { 1171 for i, testCase := range validParseTestCases { 1172 t.Run(strconv.Itoa(i), func(t *testing.T) { 1173 r := bytes.NewBufferString(testCase.input) 1174 file, errs := ParseAndEval("", r, NewScope(nil)) 1175 if len(errs) != 0 { 1176 t.Errorf("test case: %s", testCase.input) 1177 t.Errorf("unexpected errors:") 1178 for _, err := range errs { 1179 t.Errorf(" %s", err) 1180 } 1181 t.FailNow() 1182 } 1183 1184 if len(file.Defs) == len(testCase.defs) { 1185 for i := range file.Defs { 1186 if !reflect.DeepEqual(file.Defs[i], testCase.defs[i]) { 1187 t.Errorf("test case: %s", testCase.input) 1188 t.Errorf("incorrect definition %d:", i) 1189 t.Errorf(" expected: %s", testCase.defs[i]) 1190 t.Errorf(" got: %s", file.Defs[i]) 1191 } 1192 } 1193 } else { 1194 t.Errorf("test case: %s", testCase.input) 1195 t.Errorf("length mismatch, expected %d definitions, got %d", 1196 len(testCase.defs), len(file.Defs)) 1197 } 1198 1199 if len(file.Comments) == len(testCase.comments) { 1200 for i := range file.Comments { 1201 if !reflect.DeepEqual(file.Comments[i], testCase.comments[i]) { 1202 t.Errorf("test case: %s", testCase.input) 1203 t.Errorf("incorrect comment %d:", i) 1204 t.Errorf(" expected: %s", testCase.comments[i]) 1205 t.Errorf(" got: %s", file.Comments[i]) 1206 } 1207 } 1208 } else { 1209 t.Errorf("test case: %s", testCase.input) 1210 t.Errorf("length mismatch, expected %d comments, got %d", 1211 len(testCase.comments), len(file.Comments)) 1212 } 1213 }) 1214 } 1215} 1216 1217func TestParserError(t *testing.T) { 1218 testcases := []struct { 1219 name string 1220 input string 1221 err string 1222 }{ 1223 { 1224 name: "invalid first token", 1225 input: "\x00", 1226 err: "invalid character NUL", 1227 }, 1228 // TODO: test more parser errors 1229 } 1230 1231 for _, tt := range testcases { 1232 t.Run(tt.name, func(t *testing.T) { 1233 r := bytes.NewBufferString(tt.input) 1234 _, errs := ParseAndEval("", r, NewScope(nil)) 1235 if len(errs) == 0 { 1236 t.Fatalf("missing expected error") 1237 } 1238 if g, w := errs[0], tt.err; !strings.Contains(g.Error(), w) { 1239 t.Errorf("expected error %q, got %q", w, g) 1240 } 1241 for _, err := range errs[1:] { 1242 t.Errorf("got unexpected extra error %q", err) 1243 } 1244 }) 1245 } 1246} 1247 1248func TestParserEndPos(t *testing.T) { 1249 in := ` 1250 module { 1251 string: "string", 1252 stringexp: "string1" + "string2", 1253 int: -1, 1254 intexp: -1 + 2, 1255 list: ["a", "b"], 1256 listexp: ["c"] + ["d"], 1257 multilinelist: [ 1258 "e", 1259 "f", 1260 ], 1261 map: { 1262 prop: "abc", 1263 }, 1264 } 1265 ` 1266 1267 // Strip each line to make it easier to compute the previous "," from each property 1268 lines := strings.Split(in, "\n") 1269 for i := range lines { 1270 lines[i] = strings.TrimSpace(lines[i]) 1271 } 1272 in = strings.Join(lines, "\n") 1273 1274 r := bytes.NewBufferString(in) 1275 1276 file, errs := ParseAndEval("", r, NewScope(nil)) 1277 if len(errs) != 0 { 1278 t.Errorf("unexpected errors:") 1279 for _, err := range errs { 1280 t.Errorf(" %s", err) 1281 } 1282 t.FailNow() 1283 } 1284 1285 mod := file.Defs[0].(*Module) 1286 modEnd := mkpos(len(in)-1, len(lines)-1, 2) 1287 if mod.End() != modEnd { 1288 t.Errorf("expected mod.End() %s, got %s", modEnd, mod.End()) 1289 } 1290 1291 nextPos := make([]scanner.Position, len(mod.Properties)) 1292 for i := 0; i < len(mod.Properties)-1; i++ { 1293 nextPos[i] = mod.Properties[i+1].Pos() 1294 } 1295 nextPos[len(mod.Properties)-1] = mod.RBracePos 1296 1297 for i, cur := range mod.Properties { 1298 endOffset := nextPos[i].Offset - len(",\n") 1299 endLine := nextPos[i].Line - 1 1300 endColumn := len(lines[endLine-1]) // scanner.Position.Line is starts at 1 1301 endPos := mkpos(endOffset, endLine, endColumn) 1302 if cur.End() != endPos { 1303 t.Errorf("expected property %s End() %s@%d, got %s@%d", cur.Name, endPos, endPos.Offset, cur.End(), cur.End().Offset) 1304 } 1305 } 1306} 1307 1308func TestParserNotEvaluated(t *testing.T) { 1309 // When parsing without evaluation, create variables correctly 1310 scope := NewScope(nil) 1311 input := "FOO=abc\n" 1312 _, errs := Parse("", bytes.NewBufferString(input), scope) 1313 if errs != nil { 1314 t.Errorf("unexpected errors:") 1315 for _, err := range errs { 1316 t.Errorf(" %s", err) 1317 } 1318 t.FailNow() 1319 } 1320 assignment, found := scope.Get("FOO") 1321 if !found { 1322 t.Fatalf("Expected to find FOO after parsing %s", input) 1323 } 1324 if s := assignment.String(); strings.Contains(s, "PANIC") { 1325 t.Errorf("Attempt to print FOO returned %s", s) 1326 } 1327} 1328