Home | History | Annotate | Download | only in parser
      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 
     15 package parser
     16 
     17 import (
     18 	"bytes"
     19 	"reflect"
     20 	"strconv"
     21 	"strings"
     22 	"testing"
     23 	"text/scanner"
     24 )
     25 
     26 func mkpos(offset, line, column int) scanner.Position {
     27 	return scanner.Position{
     28 		Offset: offset,
     29 		Line:   line,
     30 		Column: column,
     31 	}
     32 }
     33 
     34 var 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,"]
    148 		}
    149 		`,
    150 		[]Definition{
    151 			&Module{
    152 				Type:    "foo",
    153 				TypePos: mkpos(3, 2, 3),
    154 				Map: Map{
    155 					LBracePos: mkpos(7, 2, 7),
    156 					RBracePos: mkpos(67, 5, 3),
    157 					Properties: []*Property{
    158 						{
    159 							Name:     "stuff",
    160 							NamePos:  mkpos(12, 3, 4),
    161 							ColonPos: mkpos(17, 3, 9),
    162 							Value: &List{
    163 								LBracePos: mkpos(19, 3, 11),
    164 								RBracePos: mkpos(63, 4, 19),
    165 								Values: []Expression{
    166 									&String{
    167 										LiteralPos: mkpos(20, 3, 12),
    168 										Value:      "asdf",
    169 									},
    170 									&String{
    171 										LiteralPos: mkpos(28, 3, 20),
    172 										Value:      "jkl;",
    173 									},
    174 									&String{
    175 										LiteralPos: mkpos(36, 3, 28),
    176 										Value:      "qwert",
    177 									},
    178 									&String{
    179 										LiteralPos: mkpos(49, 4, 5),
    180 										Value:      "uiop",
    181 									},
    182 									&String{
    183 										LiteralPos: mkpos(57, 4, 13),
    184 										Value:      "bnm,",
    185 									},
    186 								},
    187 							},
    188 						},
    189 					},
    190 				},
    191 			},
    192 		},
    193 		nil,
    194 	},
    195 
    196 	{`
    197 		foo {
    198 			stuff: {
    199 				isGood: true,
    200 				name: "bar",
    201 				num: 36,
    202 			}
    203 		}
    204 		`,
    205 		[]Definition{
    206 			&Module{
    207 				Type:    "foo",
    208 				TypePos: mkpos(3, 2, 3),
    209 				Map: Map{
    210 					LBracePos: mkpos(7, 2, 7),
    211 					RBracePos: mkpos(76, 8, 3),
    212 					Properties: []*Property{
    213 						{
    214 							Name:     "stuff",
    215 							NamePos:  mkpos(12, 3, 4),
    216 							ColonPos: mkpos(17, 3, 9),
    217 							Value: &Map{
    218 								LBracePos: mkpos(19, 3, 11),
    219 								RBracePos: mkpos(72, 7, 4),
    220 								Properties: []*Property{
    221 									{
    222 										Name:     "isGood",
    223 										NamePos:  mkpos(25, 4, 5),
    224 										ColonPos: mkpos(31, 4, 11),
    225 										Value: &Bool{
    226 											LiteralPos: mkpos(33, 4, 13),
    227 											Value:      true,
    228 											Token:      "true",
    229 										},
    230 									},
    231 									{
    232 										Name:     "name",
    233 										NamePos:  mkpos(43, 5, 5),
    234 										ColonPos: mkpos(47, 5, 9),
    235 										Value: &String{
    236 											LiteralPos: mkpos(49, 5, 11),
    237 											Value:      "bar",
    238 										},
    239 									},
    240 									{
    241 										Name:     "num",
    242 										NamePos:  mkpos(60, 6, 5),
    243 										ColonPos: mkpos(63, 6, 8),
    244 										Value: &Int64{
    245 											LiteralPos: mkpos(65, 6, 10),
    246 											Value:      36,
    247 											Token:      "36",
    248 										},
    249 									},
    250 								},
    251 							},
    252 						},
    253 					},
    254 				},
    255 			},
    256 		},
    257 		nil,
    258 	},
    259 
    260 	{`
    261 		// comment1
    262 		foo /* test */ {
    263 			// comment2
    264 			isGood: true,  // comment3
    265 		}
    266 		`,
    267 		[]Definition{
    268 			&Module{
    269 				Type:    "foo",
    270 				TypePos: mkpos(17, 3, 3),
    271 				Map: Map{
    272 					LBracePos: mkpos(32, 3, 18),
    273 					RBracePos: mkpos(81, 6, 3),
    274 					Properties: []*Property{
    275 						{
    276 							Name:     "isGood",
    277 							NamePos:  mkpos(52, 5, 4),
    278 							ColonPos: mkpos(58, 5, 10),
    279 							Value: &Bool{
    280 								LiteralPos: mkpos(60, 5, 12),
    281 								Value:      true,
    282 								Token:      "true",
    283 							},
    284 						},
    285 					},
    286 				},
    287 			},
    288 		},
    289 		[]*CommentGroup{
    290 			{
    291 				Comments: []*Comment{
    292 					&Comment{
    293 						Comment: []string{"// comment1"},
    294 						Slash:   mkpos(3, 2, 3),
    295 					},
    296 				},
    297 			},
    298 			{
    299 				Comments: []*Comment{
    300 					&Comment{
    301 						Comment: []string{"/* test */"},
    302 						Slash:   mkpos(21, 3, 7),
    303 					},
    304 				},
    305 			},
    306 			{
    307 				Comments: []*Comment{
    308 					&Comment{
    309 						Comment: []string{"// comment2"},
    310 						Slash:   mkpos(37, 4, 4),
    311 					},
    312 				},
    313 			},
    314 			{
    315 				Comments: []*Comment{
    316 					&Comment{
    317 						Comment: []string{"// comment3"},
    318 						Slash:   mkpos(67, 5, 19),
    319 					},
    320 				},
    321 			},
    322 		},
    323 	},
    324 
    325 	{`
    326 		foo {
    327 			name: "abc",
    328 			num: 4,
    329 		}
    330 
    331 		bar {
    332 			name: "def",
    333 			num: -5,
    334 		}
    335 		`,
    336 		[]Definition{
    337 			&Module{
    338 				Type:    "foo",
    339 				TypePos: mkpos(3, 2, 3),
    340 				Map: Map{
    341 					LBracePos: mkpos(7, 2, 7),
    342 					RBracePos: mkpos(38, 5, 3),
    343 					Properties: []*Property{
    344 						{
    345 							Name:     "name",
    346 							NamePos:  mkpos(12, 3, 4),
    347 							ColonPos: mkpos(16, 3, 8),
    348 							Value: &String{
    349 								LiteralPos: mkpos(18, 3, 10),
    350 								Value:      "abc",
    351 							},
    352 						},
    353 						{
    354 							Name:     "num",
    355 							NamePos:  mkpos(28, 4, 4),
    356 							ColonPos: mkpos(31, 4, 7),
    357 							Value: &Int64{
    358 								LiteralPos: mkpos(33, 4, 9),
    359 								Value:      4,
    360 								Token:      "4",
    361 							},
    362 						},
    363 					},
    364 				},
    365 			},
    366 			&Module{
    367 				Type:    "bar",
    368 				TypePos: mkpos(43, 7, 3),
    369 				Map: Map{
    370 					LBracePos: mkpos(47, 7, 7),
    371 					RBracePos: mkpos(79, 10, 3),
    372 					Properties: []*Property{
    373 						{
    374 							Name:     "name",
    375 							NamePos:  mkpos(52, 8, 4),
    376 							ColonPos: mkpos(56, 8, 8),
    377 							Value: &String{
    378 								LiteralPos: mkpos(58, 8, 10),
    379 								Value:      "def",
    380 							},
    381 						},
    382 						{
    383 							Name:     "num",
    384 							NamePos:  mkpos(68, 9, 4),
    385 							ColonPos: mkpos(71, 9, 7),
    386 							Value: &Int64{
    387 								LiteralPos: mkpos(73, 9, 9),
    388 								Value:      -5,
    389 								Token:      "-5",
    390 							},
    391 						},
    392 					},
    393 				},
    394 			},
    395 		},
    396 		nil,
    397 	},
    398 
    399 	{`
    400 		foo = "stuff"
    401 		bar = foo
    402 		baz = foo + bar
    403 		boo = baz
    404 		boo += foo
    405 		`,
    406 		[]Definition{
    407 			&Assignment{
    408 				Name:      "foo",
    409 				NamePos:   mkpos(3, 2, 3),
    410 				EqualsPos: mkpos(7, 2, 7),
    411 				Value: &String{
    412 					LiteralPos: mkpos(9, 2, 9),
    413 					Value:      "stuff",
    414 				},
    415 				OrigValue: &String{
    416 					LiteralPos: mkpos(9, 2, 9),
    417 					Value:      "stuff",
    418 				},
    419 				Assigner:   "=",
    420 				Referenced: true,
    421 			},
    422 			&Assignment{
    423 				Name:      "bar",
    424 				NamePos:   mkpos(19, 3, 3),
    425 				EqualsPos: mkpos(23, 3, 7),
    426 				Value: &Variable{
    427 					Name:    "foo",
    428 					NamePos: mkpos(25, 3, 9),
    429 					Value: &String{
    430 						LiteralPos: mkpos(9, 2, 9),
    431 						Value:      "stuff",
    432 					},
    433 				},
    434 				OrigValue: &Variable{
    435 					Name:    "foo",
    436 					NamePos: mkpos(25, 3, 9),
    437 					Value: &String{
    438 						LiteralPos: mkpos(9, 2, 9),
    439 						Value:      "stuff",
    440 					},
    441 				},
    442 				Assigner:   "=",
    443 				Referenced: true,
    444 			},
    445 			&Assignment{
    446 				Name:      "baz",
    447 				NamePos:   mkpos(31, 4, 3),
    448 				EqualsPos: mkpos(35, 4, 7),
    449 				Value: &Operator{
    450 					OperatorPos: mkpos(41, 4, 13),
    451 					Operator:    '+',
    452 					Value: &String{
    453 						LiteralPos: mkpos(9, 2, 9),
    454 						Value:      "stuffstuff",
    455 					},
    456 					Args: [2]Expression{
    457 						&Variable{
    458 							Name:    "foo",
    459 							NamePos: mkpos(37, 4, 9),
    460 							Value: &String{
    461 								LiteralPos: mkpos(9, 2, 9),
    462 								Value:      "stuff",
    463 							},
    464 						},
    465 						&Variable{
    466 							Name:    "bar",
    467 							NamePos: mkpos(43, 4, 15),
    468 							Value: &Variable{
    469 								Name:    "foo",
    470 								NamePos: mkpos(25, 3, 9),
    471 								Value: &String{
    472 									LiteralPos: mkpos(9, 2, 9),
    473 									Value:      "stuff",
    474 								},
    475 							},
    476 						},
    477 					},
    478 				},
    479 				OrigValue: &Operator{
    480 					OperatorPos: mkpos(41, 4, 13),
    481 					Operator:    '+',
    482 					Value: &String{
    483 						LiteralPos: mkpos(9, 2, 9),
    484 						Value:      "stuffstuff",
    485 					},
    486 					Args: [2]Expression{
    487 						&Variable{
    488 							Name:    "foo",
    489 							NamePos: mkpos(37, 4, 9),
    490 							Value: &String{
    491 								LiteralPos: mkpos(9, 2, 9),
    492 								Value:      "stuff",
    493 							},
    494 						},
    495 						&Variable{
    496 							Name:    "bar",
    497 							NamePos: mkpos(43, 4, 15),
    498 							Value: &Variable{
    499 								Name:    "foo",
    500 								NamePos: mkpos(25, 3, 9),
    501 								Value: &String{
    502 									LiteralPos: mkpos(9, 2, 9),
    503 									Value:      "stuff",
    504 								},
    505 							},
    506 						},
    507 					},
    508 				},
    509 				Assigner:   "=",
    510 				Referenced: true,
    511 			},
    512 			&Assignment{
    513 				Name:      "boo",
    514 				NamePos:   mkpos(49, 5, 3),
    515 				EqualsPos: mkpos(53, 5, 7),
    516 				Value: &Operator{
    517 					Args: [2]Expression{
    518 						&Variable{
    519 							Name:    "baz",
    520 							NamePos: mkpos(55, 5, 9),
    521 							Value: &Operator{
    522 								OperatorPos: mkpos(41, 4, 13),
    523 								Operator:    '+',
    524 								Value: &String{
    525 									LiteralPos: mkpos(9, 2, 9),
    526 									Value:      "stuffstuff",
    527 								},
    528 								Args: [2]Expression{
    529 									&Variable{
    530 										Name:    "foo",
    531 										NamePos: mkpos(37, 4, 9),
    532 										Value: &String{
    533 											LiteralPos: mkpos(9, 2, 9),
    534 											Value:      "stuff",
    535 										},
    536 									},
    537 									&Variable{
    538 										Name:    "bar",
    539 										NamePos: mkpos(43, 4, 15),
    540 										Value: &Variable{
    541 											Name:    "foo",
    542 											NamePos: mkpos(25, 3, 9),
    543 											Value: &String{
    544 												LiteralPos: mkpos(9, 2, 9),
    545 												Value:      "stuff",
    546 											},
    547 										},
    548 									},
    549 								},
    550 							},
    551 						},
    552 						&Variable{
    553 							Name:    "foo",
    554 							NamePos: mkpos(68, 6, 10),
    555 							Value: &String{
    556 								LiteralPos: mkpos(9, 2, 9),
    557 								Value:      "stuff",
    558 							},
    559 						},
    560 					},
    561 					OperatorPos: mkpos(66, 6, 8),
    562 					Operator:    '+',
    563 					Value: &String{
    564 						LiteralPos: mkpos(9, 2, 9),
    565 						Value:      "stuffstuffstuff",
    566 					},
    567 				},
    568 				OrigValue: &Variable{
    569 					Name:    "baz",
    570 					NamePos: mkpos(55, 5, 9),
    571 					Value: &Operator{
    572 						OperatorPos: mkpos(41, 4, 13),
    573 						Operator:    '+',
    574 						Value: &String{
    575 							LiteralPos: mkpos(9, 2, 9),
    576 							Value:      "stuffstuff",
    577 						},
    578 						Args: [2]Expression{
    579 							&Variable{
    580 								Name:    "foo",
    581 								NamePos: mkpos(37, 4, 9),
    582 								Value: &String{
    583 									LiteralPos: mkpos(9, 2, 9),
    584 									Value:      "stuff",
    585 								},
    586 							},
    587 							&Variable{
    588 								Name:    "bar",
    589 								NamePos: mkpos(43, 4, 15),
    590 								Value: &Variable{
    591 									Name:    "foo",
    592 									NamePos: mkpos(25, 3, 9),
    593 									Value: &String{
    594 										LiteralPos: mkpos(9, 2, 9),
    595 										Value:      "stuff",
    596 									},
    597 								},
    598 							},
    599 						},
    600 					},
    601 				},
    602 				Assigner: "=",
    603 			},
    604 			&Assignment{
    605 				Name:      "boo",
    606 				NamePos:   mkpos(61, 6, 3),
    607 				EqualsPos: mkpos(66, 6, 8),
    608 				Value: &Variable{
    609 					Name:    "foo",
    610 					NamePos: mkpos(68, 6, 10),
    611 					Value: &String{
    612 						LiteralPos: mkpos(9, 2, 9),
    613 						Value:      "stuff",
    614 					},
    615 				},
    616 				OrigValue: &Variable{
    617 					Name:    "foo",
    618 					NamePos: mkpos(68, 6, 10),
    619 					Value: &String{
    620 						LiteralPos: mkpos(9, 2, 9),
    621 						Value:      "stuff",
    622 					},
    623 				},
    624 				Assigner: "+=",
    625 			},
    626 		},
    627 		nil,
    628 	},
    629 
    630 	{`
    631 		baz = -4 + -5 + 6
    632 		`,
    633 		[]Definition{
    634 			&Assignment{
    635 				Name:      "baz",
    636 				NamePos:   mkpos(3, 2, 3),
    637 				EqualsPos: mkpos(7, 2, 7),
    638 				Value: &Operator{
    639 					OperatorPos: mkpos(12, 2, 12),
    640 					Operator:    '+',
    641 					Value: &Int64{
    642 						LiteralPos: mkpos(9, 2, 9),
    643 						Value:      -3,
    644 					},
    645 					Args: [2]Expression{
    646 						&Int64{
    647 							LiteralPos: mkpos(9, 2, 9),
    648 							Value:      -4,
    649 							Token:      "-4",
    650 						},
    651 						&Operator{
    652 							OperatorPos: mkpos(17, 2, 17),
    653 							Operator:    '+',
    654 							Value: &Int64{
    655 								LiteralPos: mkpos(14, 2, 14),
    656 								Value:      1,
    657 							},
    658 							Args: [2]Expression{
    659 								&Int64{
    660 									LiteralPos: mkpos(14, 2, 14),
    661 									Value:      -5,
    662 									Token:      "-5",
    663 								},
    664 								&Int64{
    665 									LiteralPos: mkpos(19, 2, 19),
    666 									Value:      6,
    667 									Token:      "6",
    668 								},
    669 							},
    670 						},
    671 					},
    672 				},
    673 				OrigValue: &Operator{
    674 					OperatorPos: mkpos(12, 2, 12),
    675 					Operator:    '+',
    676 					Value: &Int64{
    677 						LiteralPos: mkpos(9, 2, 9),
    678 						Value:      -3,
    679 					},
    680 					Args: [2]Expression{
    681 						&Int64{
    682 							LiteralPos: mkpos(9, 2, 9),
    683 							Value:      -4,
    684 							Token:      "-4",
    685 						},
    686 						&Operator{
    687 							OperatorPos: mkpos(17, 2, 17),
    688 							Operator:    '+',
    689 							Value: &Int64{
    690 								LiteralPos: mkpos(14, 2, 14),
    691 								Value:      1,
    692 							},
    693 							Args: [2]Expression{
    694 								&Int64{
    695 									LiteralPos: mkpos(14, 2, 14),
    696 									Value:      -5,
    697 									Token:      "-5",
    698 								},
    699 								&Int64{
    700 									LiteralPos: mkpos(19, 2, 19),
    701 									Value:      6,
    702 									Token:      "6",
    703 								},
    704 							},
    705 						},
    706 					},
    707 				},
    708 				Assigner:   "=",
    709 				Referenced: false,
    710 			},
    711 		},
    712 		nil,
    713 	},
    714 
    715 	{`
    716 		foo = 1000000
    717 		bar = foo
    718 		baz = foo + bar
    719 		boo = baz
    720 		boo += foo
    721 		`,
    722 		[]Definition{
    723 			&Assignment{
    724 				Name:      "foo",
    725 				NamePos:   mkpos(3, 2, 3),
    726 				EqualsPos: mkpos(7, 2, 7),
    727 				Value: &Int64{
    728 					LiteralPos: mkpos(9, 2, 9),
    729 					Value:      1000000,
    730 					Token:      "1000000",
    731 				},
    732 				OrigValue: &Int64{
    733 					LiteralPos: mkpos(9, 2, 9),
    734 					Value:      1000000,
    735 					Token:      "1000000",
    736 				},
    737 				Assigner:   "=",
    738 				Referenced: true,
    739 			},
    740 			&Assignment{
    741 				Name:      "bar",
    742 				NamePos:   mkpos(19, 3, 3),
    743 				EqualsPos: mkpos(23, 3, 7),
    744 				Value: &Variable{
    745 					Name:    "foo",
    746 					NamePos: mkpos(25, 3, 9),
    747 					Value: &Int64{
    748 						LiteralPos: mkpos(9, 2, 9),
    749 						Value:      1000000,
    750 						Token:      "1000000",
    751 					},
    752 				},
    753 				OrigValue: &Variable{
    754 					Name:    "foo",
    755 					NamePos: mkpos(25, 3, 9),
    756 					Value: &Int64{
    757 						LiteralPos: mkpos(9, 2, 9),
    758 						Value:      1000000,
    759 						Token:      "1000000",
    760 					},
    761 				},
    762 				Assigner:   "=",
    763 				Referenced: true,
    764 			},
    765 			&Assignment{
    766 				Name:      "baz",
    767 				NamePos:   mkpos(31, 4, 3),
    768 				EqualsPos: mkpos(35, 4, 7),
    769 				Value: &Operator{
    770 					OperatorPos: mkpos(41, 4, 13),
    771 					Operator:    '+',
    772 					Value: &Int64{
    773 						LiteralPos: mkpos(9, 2, 9),
    774 						Value:      2000000,
    775 					},
    776 					Args: [2]Expression{
    777 						&Variable{
    778 							Name:    "foo",
    779 							NamePos: mkpos(37, 4, 9),
    780 							Value: &Int64{
    781 								LiteralPos: mkpos(9, 2, 9),
    782 								Value:      1000000,
    783 								Token:      "1000000",
    784 							},
    785 						},
    786 						&Variable{
    787 							Name:    "bar",
    788 							NamePos: mkpos(43, 4, 15),
    789 							Value: &Variable{
    790 								Name:    "foo",
    791 								NamePos: mkpos(25, 3, 9),
    792 								Value: &Int64{
    793 									LiteralPos: mkpos(9, 2, 9),
    794 									Value:      1000000,
    795 									Token:      "1000000",
    796 								},
    797 							},
    798 						},
    799 					},
    800 				},
    801 				OrigValue: &Operator{
    802 					OperatorPos: mkpos(41, 4, 13),
    803 					Operator:    '+',
    804 					Value: &Int64{
    805 						LiteralPos: mkpos(9, 2, 9),
    806 						Value:      2000000,
    807 					},
    808 					Args: [2]Expression{
    809 						&Variable{
    810 							Name:    "foo",
    811 							NamePos: mkpos(37, 4, 9),
    812 							Value: &Int64{
    813 								LiteralPos: mkpos(9, 2, 9),
    814 								Value:      1000000,
    815 								Token:      "1000000",
    816 							},
    817 						},
    818 						&Variable{
    819 							Name:    "bar",
    820 							NamePos: mkpos(43, 4, 15),
    821 							Value: &Variable{
    822 								Name:    "foo",
    823 								NamePos: mkpos(25, 3, 9),
    824 								Value: &Int64{
    825 									LiteralPos: mkpos(9, 2, 9),
    826 									Value:      1000000,
    827 									Token:      "1000000",
    828 								},
    829 							},
    830 						},
    831 					},
    832 				},
    833 				Assigner:   "=",
    834 				Referenced: true,
    835 			},
    836 			&Assignment{
    837 				Name:      "boo",
    838 				NamePos:   mkpos(49, 5, 3),
    839 				EqualsPos: mkpos(53, 5, 7),
    840 				Value: &Operator{
    841 					Args: [2]Expression{
    842 						&Variable{
    843 							Name:    "baz",
    844 							NamePos: mkpos(55, 5, 9),
    845 							Value: &Operator{
    846 								OperatorPos: mkpos(41, 4, 13),
    847 								Operator:    '+',
    848 								Value: &Int64{
    849 									LiteralPos: mkpos(9, 2, 9),
    850 									Value:      2000000,
    851 								},
    852 								Args: [2]Expression{
    853 									&Variable{
    854 										Name:    "foo",
    855 										NamePos: mkpos(37, 4, 9),
    856 										Value: &Int64{
    857 											LiteralPos: mkpos(9, 2, 9),
    858 											Value:      1000000,
    859 											Token:      "1000000",
    860 										},
    861 									},
    862 									&Variable{
    863 										Name:    "bar",
    864 										NamePos: mkpos(43, 4, 15),
    865 										Value: &Variable{
    866 											Name:    "foo",
    867 											NamePos: mkpos(25, 3, 9),
    868 											Value: &Int64{
    869 												LiteralPos: mkpos(9, 2, 9),
    870 												Value:      1000000,
    871 												Token:      "1000000",
    872 											},
    873 										},
    874 									},
    875 								},
    876 							},
    877 						},
    878 						&Variable{
    879 							Name:    "foo",
    880 							NamePos: mkpos(68, 6, 10),
    881 							Value: &Int64{
    882 								LiteralPos: mkpos(9, 2, 9),
    883 								Value:      1000000,
    884 								Token:      "1000000",
    885 							},
    886 						},
    887 					},
    888 					OperatorPos: mkpos(66, 6, 8),
    889 					Operator:    '+',
    890 					Value: &Int64{
    891 						LiteralPos: mkpos(9, 2, 9),
    892 						Value:      3000000,
    893 					},
    894 				},
    895 				OrigValue: &Variable{
    896 					Name:    "baz",
    897 					NamePos: mkpos(55, 5, 9),
    898 					Value: &Operator{
    899 						OperatorPos: mkpos(41, 4, 13),
    900 						Operator:    '+',
    901 						Value: &Int64{
    902 							LiteralPos: mkpos(9, 2, 9),
    903 							Value:      2000000,
    904 						},
    905 						Args: [2]Expression{
    906 							&Variable{
    907 								Name:    "foo",
    908 								NamePos: mkpos(37, 4, 9),
    909 								Value: &Int64{
    910 									LiteralPos: mkpos(9, 2, 9),
    911 									Value:      1000000,
    912 									Token:      "1000000",
    913 								},
    914 							},
    915 							&Variable{
    916 								Name:    "bar",
    917 								NamePos: mkpos(43, 4, 15),
    918 								Value: &Variable{
    919 									Name:    "foo",
    920 									NamePos: mkpos(25, 3, 9),
    921 									Value: &Int64{
    922 										LiteralPos: mkpos(9, 2, 9),
    923 										Value:      1000000,
    924 										Token:      "1000000",
    925 									},
    926 								},
    927 							},
    928 						},
    929 					},
    930 				},
    931 				Assigner: "=",
    932 			},
    933 			&Assignment{
    934 				Name:      "boo",
    935 				NamePos:   mkpos(61, 6, 3),
    936 				EqualsPos: mkpos(66, 6, 8),
    937 				Value: &Variable{
    938 					Name:    "foo",
    939 					NamePos: mkpos(68, 6, 10),
    940 					Value: &Int64{
    941 						LiteralPos: mkpos(9, 2, 9),
    942 						Value:      1000000,
    943 						Token:      "1000000",
    944 					},
    945 				},
    946 				OrigValue: &Variable{
    947 					Name:    "foo",
    948 					NamePos: mkpos(68, 6, 10),
    949 					Value: &Int64{
    950 						LiteralPos: mkpos(9, 2, 9),
    951 						Value:      1000000,
    952 						Token:      "1000000",
    953 					},
    954 				},
    955 				Assigner: "+=",
    956 			},
    957 		},
    958 		nil,
    959 	},
    960 
    961 	{`
    962 		// comment1
    963 		// comment2
    964 
    965 		/* comment3
    966 		   comment4 */
    967 		// comment5
    968 
    969 		/* comment6 */ /* comment7 */ // comment8
    970 		`,
    971 		nil,
    972 		[]*CommentGroup{
    973 			{
    974 				Comments: []*Comment{
    975 					&Comment{
    976 						Comment: []string{"// comment1"},
    977 						Slash:   mkpos(3, 2, 3),
    978 					},
    979 					&Comment{
    980 						Comment: []string{"// comment2"},
    981 						Slash:   mkpos(17, 3, 3),
    982 					},
    983 				},
    984 			},
    985 			{
    986 				Comments: []*Comment{
    987 					&Comment{
    988 						Comment: []string{"/* comment3", "		   comment4 */"},
    989 						Slash: mkpos(32, 5, 3),
    990 					},
    991 					&Comment{
    992 						Comment: []string{"// comment5"},
    993 						Slash:   mkpos(63, 7, 3),
    994 					},
    995 				},
    996 			},
    997 			{
    998 				Comments: []*Comment{
    999 					&Comment{
   1000 						Comment: []string{"/* comment6 */"},
   1001 						Slash:   mkpos(78, 9, 3),
   1002 					},
   1003 					&Comment{
   1004 						Comment: []string{"/* comment7 */"},
   1005 						Slash:   mkpos(93, 9, 18),
   1006 					},
   1007 					&Comment{
   1008 						Comment: []string{"// comment8"},
   1009 						Slash:   mkpos(108, 9, 33),
   1010 					},
   1011 				},
   1012 			},
   1013 		},
   1014 	},
   1015 }
   1016 
   1017 func TestParseValidInput(t *testing.T) {
   1018 	for i, testCase := range validParseTestCases {
   1019 		t.Run(strconv.Itoa(i), func(t *testing.T) {
   1020 			r := bytes.NewBufferString(testCase.input)
   1021 			file, errs := ParseAndEval("", r, NewScope(nil))
   1022 			if len(errs) != 0 {
   1023 				t.Errorf("test case: %s", testCase.input)
   1024 				t.Errorf("unexpected errors:")
   1025 				for _, err := range errs {
   1026 					t.Errorf("  %s", err)
   1027 				}
   1028 				t.FailNow()
   1029 			}
   1030 
   1031 			if len(file.Defs) == len(testCase.defs) {
   1032 				for i := range file.Defs {
   1033 					if !reflect.DeepEqual(file.Defs[i], testCase.defs[i]) {
   1034 						t.Errorf("test case: %s", testCase.input)
   1035 						t.Errorf("incorrect defintion %d:", i)
   1036 						t.Errorf("  expected: %s", testCase.defs[i])
   1037 						t.Errorf("       got: %s", file.Defs[i])
   1038 					}
   1039 				}
   1040 			} else {
   1041 				t.Errorf("test case: %s", testCase.input)
   1042 				t.Errorf("length mismatch, expected %d definitions, got %d",
   1043 					len(testCase.defs), len(file.Defs))
   1044 			}
   1045 
   1046 			if len(file.Comments) == len(testCase.comments) {
   1047 				for i := range file.Comments {
   1048 					if !reflect.DeepEqual(file.Comments[i], testCase.comments[i]) {
   1049 						t.Errorf("test case: %s", testCase.input)
   1050 						t.Errorf("incorrect comment %d:", i)
   1051 						t.Errorf("  expected: %s", testCase.comments[i])
   1052 						t.Errorf("       got: %s", file.Comments[i])
   1053 					}
   1054 				}
   1055 			} else {
   1056 				t.Errorf("test case: %s", testCase.input)
   1057 				t.Errorf("length mismatch, expected %d comments, got %d",
   1058 					len(testCase.comments), len(file.Comments))
   1059 			}
   1060 		})
   1061 	}
   1062 }
   1063 
   1064 // TODO: Test error strings
   1065 
   1066 func TestParserEndPos(t *testing.T) {
   1067 	in := `
   1068 		module {
   1069 			string: "string",
   1070 			stringexp: "string1" + "string2",
   1071 			int: -1,
   1072 			intexp: -1 + 2,
   1073 			list: ["a", "b"],
   1074 			listexp: ["c"] + ["d"],
   1075 			multilinelist: [
   1076 				"e",
   1077 				"f",
   1078 			],
   1079 			map: {
   1080 				prop: "abc",
   1081 			},
   1082 		}
   1083 	`
   1084 
   1085 	// Strip each line to make it easier to compute the previous "," from each property
   1086 	lines := strings.Split(in, "\n")
   1087 	for i := range lines {
   1088 		lines[i] = strings.TrimSpace(lines[i])
   1089 	}
   1090 	in = strings.Join(lines, "\n")
   1091 
   1092 	r := bytes.NewBufferString(in)
   1093 
   1094 	file, errs := ParseAndEval("", r, NewScope(nil))
   1095 	if len(errs) != 0 {
   1096 		t.Errorf("unexpected errors:")
   1097 		for _, err := range errs {
   1098 			t.Errorf("  %s", err)
   1099 		}
   1100 		t.FailNow()
   1101 	}
   1102 
   1103 	mod := file.Defs[0].(*Module)
   1104 	modEnd := mkpos(len(in)-1, len(lines)-1, 2)
   1105 	if mod.End() != modEnd {
   1106 		t.Errorf("expected mod.End() %s, got %s", modEnd, mod.End())
   1107 	}
   1108 
   1109 	nextPos := make([]scanner.Position, len(mod.Properties))
   1110 	for i := 0; i < len(mod.Properties)-1; i++ {
   1111 		nextPos[i] = mod.Properties[i+1].Pos()
   1112 	}
   1113 	nextPos[len(mod.Properties)-1] = mod.RBracePos
   1114 
   1115 	for i, cur := range mod.Properties {
   1116 		endOffset := nextPos[i].Offset - len(",\n")
   1117 		endLine := nextPos[i].Line - 1
   1118 		endColumn := len(lines[endLine-1]) // scanner.Position.Line is starts at 1
   1119 		endPos := mkpos(endOffset, endLine, endColumn)
   1120 		if cur.End() != endPos {
   1121 			t.Errorf("expected property %s End() %s@%d, got %s@%d", cur.Name, endPos, endPos.Offset, cur.End(), cur.End().Offset)
   1122 		}
   1123 	}
   1124 }
   1125