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