• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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