xref: /aosp_15_r20/external/starlark-go/syntax/parse_test.go (revision 4947cdc739c985f6d86941e22894f5cefe7c9e9a)
1*4947cdc7SCole Faust// Copyright 2017 The Bazel Authors. All rights reserved.
2*4947cdc7SCole Faust// Use of this source code is governed by a BSD-style
3*4947cdc7SCole Faust// license that can be found in the LICENSE file.
4*4947cdc7SCole Faust
5*4947cdc7SCole Faustpackage syntax_test
6*4947cdc7SCole Faust
7*4947cdc7SCole Faustimport (
8*4947cdc7SCole Faust	"bufio"
9*4947cdc7SCole Faust	"bytes"
10*4947cdc7SCole Faust	"fmt"
11*4947cdc7SCole Faust	"go/build"
12*4947cdc7SCole Faust	"io/ioutil"
13*4947cdc7SCole Faust	"path/filepath"
14*4947cdc7SCole Faust	"reflect"
15*4947cdc7SCole Faust	"strings"
16*4947cdc7SCole Faust	"testing"
17*4947cdc7SCole Faust
18*4947cdc7SCole Faust	"go.starlark.net/internal/chunkedfile"
19*4947cdc7SCole Faust	"go.starlark.net/starlarktest"
20*4947cdc7SCole Faust	"go.starlark.net/syntax"
21*4947cdc7SCole Faust)
22*4947cdc7SCole Faust
23*4947cdc7SCole Faustfunc TestExprParseTrees(t *testing.T) {
24*4947cdc7SCole Faust	for _, test := range []struct {
25*4947cdc7SCole Faust		input, want string
26*4947cdc7SCole Faust	}{
27*4947cdc7SCole Faust		{`print(1)`,
28*4947cdc7SCole Faust			`(CallExpr Fn=print Args=(1))`},
29*4947cdc7SCole Faust		{"print(1)\n",
30*4947cdc7SCole Faust			`(CallExpr Fn=print Args=(1))`},
31*4947cdc7SCole Faust		{`x + 1`,
32*4947cdc7SCole Faust			`(BinaryExpr X=x Op=+ Y=1)`},
33*4947cdc7SCole Faust		{`[x for x in y]`,
34*4947cdc7SCole Faust			`(Comprehension Body=x Clauses=((ForClause Vars=x X=y)))`},
35*4947cdc7SCole Faust		{`[x for x in (a if b else c)]`,
36*4947cdc7SCole Faust			`(Comprehension Body=x Clauses=((ForClause Vars=x X=(ParenExpr X=(CondExpr Cond=b True=a False=c)))))`},
37*4947cdc7SCole Faust		{`x[i].f(42)`,
38*4947cdc7SCole Faust			`(CallExpr Fn=(DotExpr X=(IndexExpr X=x Y=i) Name=f) Args=(42))`},
39*4947cdc7SCole Faust		{`x.f()`,
40*4947cdc7SCole Faust			`(CallExpr Fn=(DotExpr X=x Name=f))`},
41*4947cdc7SCole Faust		{`x+y*z`,
42*4947cdc7SCole Faust			`(BinaryExpr X=x Op=+ Y=(BinaryExpr X=y Op=* Y=z))`},
43*4947cdc7SCole Faust		{`x%y-z`,
44*4947cdc7SCole Faust			`(BinaryExpr X=(BinaryExpr X=x Op=% Y=y) Op=- Y=z)`},
45*4947cdc7SCole Faust		{`a + b not in c`,
46*4947cdc7SCole Faust			`(BinaryExpr X=(BinaryExpr X=a Op=+ Y=b) Op=not in Y=c)`},
47*4947cdc7SCole Faust		{`lambda x, *args, **kwargs: None`,
48*4947cdc7SCole Faust			`(LambdaExpr Params=(x (UnaryExpr Op=* X=args) (UnaryExpr Op=** X=kwargs)) Body=None)`},
49*4947cdc7SCole Faust		{`{"one": 1}`,
50*4947cdc7SCole Faust			`(DictExpr List=((DictEntry Key="one" Value=1)))`},
51*4947cdc7SCole Faust		{`a[i]`,
52*4947cdc7SCole Faust			`(IndexExpr X=a Y=i)`},
53*4947cdc7SCole Faust		{`a[i:]`,
54*4947cdc7SCole Faust			`(SliceExpr X=a Lo=i)`},
55*4947cdc7SCole Faust		{`a[:j]`,
56*4947cdc7SCole Faust			`(SliceExpr X=a Hi=j)`},
57*4947cdc7SCole Faust		{`a[::]`,
58*4947cdc7SCole Faust			`(SliceExpr X=a)`},
59*4947cdc7SCole Faust		{`a[::k]`,
60*4947cdc7SCole Faust			`(SliceExpr X=a Step=k)`},
61*4947cdc7SCole Faust		{`[]`,
62*4947cdc7SCole Faust			`(ListExpr)`},
63*4947cdc7SCole Faust		{`[1]`,
64*4947cdc7SCole Faust			`(ListExpr List=(1))`},
65*4947cdc7SCole Faust		{`[1,]`,
66*4947cdc7SCole Faust			`(ListExpr List=(1))`},
67*4947cdc7SCole Faust		{`[1, 2]`,
68*4947cdc7SCole Faust			`(ListExpr List=(1 2))`},
69*4947cdc7SCole Faust		{`()`,
70*4947cdc7SCole Faust			`(TupleExpr)`},
71*4947cdc7SCole Faust		{`(4,)`,
72*4947cdc7SCole Faust			`(ParenExpr X=(TupleExpr List=(4)))`},
73*4947cdc7SCole Faust		{`(4)`,
74*4947cdc7SCole Faust			`(ParenExpr X=4)`},
75*4947cdc7SCole Faust		{`(4, 5)`,
76*4947cdc7SCole Faust			`(ParenExpr X=(TupleExpr List=(4 5)))`},
77*4947cdc7SCole Faust		{`1, 2, 3`,
78*4947cdc7SCole Faust			`(TupleExpr List=(1 2 3))`},
79*4947cdc7SCole Faust		{`1, 2,`,
80*4947cdc7SCole Faust			`unparenthesized tuple with trailing comma`},
81*4947cdc7SCole Faust		{`{}`,
82*4947cdc7SCole Faust			`(DictExpr)`},
83*4947cdc7SCole Faust		{`{"a": 1}`,
84*4947cdc7SCole Faust			`(DictExpr List=((DictEntry Key="a" Value=1)))`},
85*4947cdc7SCole Faust		{`{"a": 1,}`,
86*4947cdc7SCole Faust			`(DictExpr List=((DictEntry Key="a" Value=1)))`},
87*4947cdc7SCole Faust		{`{"a": 1, "b": 2}`,
88*4947cdc7SCole Faust			`(DictExpr List=((DictEntry Key="a" Value=1) (DictEntry Key="b" Value=2)))`},
89*4947cdc7SCole Faust		{`{x: y for (x, y) in z}`,
90*4947cdc7SCole Faust			`(Comprehension Curly Body=(DictEntry Key=x Value=y) Clauses=((ForClause Vars=(ParenExpr X=(TupleExpr List=(x y))) X=z)))`},
91*4947cdc7SCole Faust		{`{x: y for a in b if c}`,
92*4947cdc7SCole Faust			`(Comprehension Curly Body=(DictEntry Key=x Value=y) Clauses=((ForClause Vars=a X=b) (IfClause Cond=c)))`},
93*4947cdc7SCole Faust		{`-1 + +2`,
94*4947cdc7SCole Faust			`(BinaryExpr X=(UnaryExpr Op=- X=1) Op=+ Y=(UnaryExpr Op=+ X=2))`},
95*4947cdc7SCole Faust		{`"foo" + "bar"`,
96*4947cdc7SCole Faust			`(BinaryExpr X="foo" Op=+ Y="bar")`},
97*4947cdc7SCole Faust		{`-1 * 2`, // prec(unary -) > prec(binary *)
98*4947cdc7SCole Faust			`(BinaryExpr X=(UnaryExpr Op=- X=1) Op=* Y=2)`},
99*4947cdc7SCole Faust		{`-x[i]`, // prec(unary -) < prec(x[i])
100*4947cdc7SCole Faust			`(UnaryExpr Op=- X=(IndexExpr X=x Y=i))`},
101*4947cdc7SCole Faust		{`a | b & c | d`, // prec(|) < prec(&)
102*4947cdc7SCole Faust			`(BinaryExpr X=(BinaryExpr X=a Op=| Y=(BinaryExpr X=b Op=& Y=c)) Op=| Y=d)`},
103*4947cdc7SCole Faust		{`a or b and c or d`,
104*4947cdc7SCole Faust			`(BinaryExpr X=(BinaryExpr X=a Op=or Y=(BinaryExpr X=b Op=and Y=c)) Op=or Y=d)`},
105*4947cdc7SCole Faust		{`a and b or c and d`,
106*4947cdc7SCole Faust			`(BinaryExpr X=(BinaryExpr X=a Op=and Y=b) Op=or Y=(BinaryExpr X=c Op=and Y=d))`},
107*4947cdc7SCole Faust		{`f(1, x=y)`,
108*4947cdc7SCole Faust			`(CallExpr Fn=f Args=(1 (BinaryExpr X=x Op== Y=y)))`},
109*4947cdc7SCole Faust		{`f(*args, **kwargs)`,
110*4947cdc7SCole Faust			`(CallExpr Fn=f Args=((UnaryExpr Op=* X=args) (UnaryExpr Op=** X=kwargs)))`},
111*4947cdc7SCole Faust		{`lambda *args, *, x=1, **kwargs: 0`,
112*4947cdc7SCole Faust			`(LambdaExpr Params=((UnaryExpr Op=* X=args) (UnaryExpr Op=*) (BinaryExpr X=x Op== Y=1) (UnaryExpr Op=** X=kwargs)) Body=0)`},
113*4947cdc7SCole Faust		{`lambda *, a, *b: 0`,
114*4947cdc7SCole Faust			`(LambdaExpr Params=((UnaryExpr Op=*) a (UnaryExpr Op=* X=b)) Body=0)`},
115*4947cdc7SCole Faust		{`a if b else c`,
116*4947cdc7SCole Faust			`(CondExpr Cond=b True=a False=c)`},
117*4947cdc7SCole Faust		{`a and not b`,
118*4947cdc7SCole Faust			`(BinaryExpr X=a Op=and Y=(UnaryExpr Op=not X=b))`},
119*4947cdc7SCole Faust		{`[e for x in y if cond1 if cond2]`,
120*4947cdc7SCole Faust			`(Comprehension Body=e Clauses=((ForClause Vars=x X=y) (IfClause Cond=cond1) (IfClause Cond=cond2)))`}, // github.com/google/skylark/issues/53
121*4947cdc7SCole Faust	} {
122*4947cdc7SCole Faust		e, err := syntax.ParseExpr("foo.star", test.input, 0)
123*4947cdc7SCole Faust		var got string
124*4947cdc7SCole Faust		if err != nil {
125*4947cdc7SCole Faust			got = stripPos(err)
126*4947cdc7SCole Faust		} else {
127*4947cdc7SCole Faust			got = treeString(e)
128*4947cdc7SCole Faust		}
129*4947cdc7SCole Faust		if test.want != got {
130*4947cdc7SCole Faust			t.Errorf("parse `%s` = %s, want %s", test.input, got, test.want)
131*4947cdc7SCole Faust		}
132*4947cdc7SCole Faust	}
133*4947cdc7SCole Faust}
134*4947cdc7SCole Faust
135*4947cdc7SCole Faustfunc TestStmtParseTrees(t *testing.T) {
136*4947cdc7SCole Faust	for _, test := range []struct {
137*4947cdc7SCole Faust		input, want string
138*4947cdc7SCole Faust	}{
139*4947cdc7SCole Faust		{`print(1)`,
140*4947cdc7SCole Faust			`(ExprStmt X=(CallExpr Fn=print Args=(1)))`},
141*4947cdc7SCole Faust		{`return 1, 2`,
142*4947cdc7SCole Faust			`(ReturnStmt Result=(TupleExpr List=(1 2)))`},
143*4947cdc7SCole Faust		{`return`,
144*4947cdc7SCole Faust			`(ReturnStmt)`},
145*4947cdc7SCole Faust		{`for i in "abc": break`,
146*4947cdc7SCole Faust			`(ForStmt Vars=i X="abc" Body=((BranchStmt Token=break)))`},
147*4947cdc7SCole Faust		{`for i in "abc": continue`,
148*4947cdc7SCole Faust			`(ForStmt Vars=i X="abc" Body=((BranchStmt Token=continue)))`},
149*4947cdc7SCole Faust		{`for x, y in z: pass`,
150*4947cdc7SCole Faust			`(ForStmt Vars=(TupleExpr List=(x y)) X=z Body=((BranchStmt Token=pass)))`},
151*4947cdc7SCole Faust		{`if True: pass`,
152*4947cdc7SCole Faust			`(IfStmt Cond=True True=((BranchStmt Token=pass)))`},
153*4947cdc7SCole Faust		{`if True: break`,
154*4947cdc7SCole Faust			`(IfStmt Cond=True True=((BranchStmt Token=break)))`},
155*4947cdc7SCole Faust		{`if True: continue`,
156*4947cdc7SCole Faust			`(IfStmt Cond=True True=((BranchStmt Token=continue)))`},
157*4947cdc7SCole Faust		{`if True: pass
158*4947cdc7SCole Faustelse:
159*4947cdc7SCole Faust	pass`,
160*4947cdc7SCole Faust			`(IfStmt Cond=True True=((BranchStmt Token=pass)) False=((BranchStmt Token=pass)))`},
161*4947cdc7SCole Faust		{"if a: pass\nelif b: pass\nelse: pass",
162*4947cdc7SCole Faust			`(IfStmt Cond=a True=((BranchStmt Token=pass)) False=((IfStmt Cond=b True=((BranchStmt Token=pass)) False=((BranchStmt Token=pass)))))`},
163*4947cdc7SCole Faust		{`x, y = 1, 2`,
164*4947cdc7SCole Faust			`(AssignStmt Op== LHS=(TupleExpr List=(x y)) RHS=(TupleExpr List=(1 2)))`},
165*4947cdc7SCole Faust		{`x[i] = 1`,
166*4947cdc7SCole Faust			`(AssignStmt Op== LHS=(IndexExpr X=x Y=i) RHS=1)`},
167*4947cdc7SCole Faust		{`x.f = 1`,
168*4947cdc7SCole Faust			`(AssignStmt Op== LHS=(DotExpr X=x Name=f) RHS=1)`},
169*4947cdc7SCole Faust		{`(x, y) = 1`,
170*4947cdc7SCole Faust			`(AssignStmt Op== LHS=(ParenExpr X=(TupleExpr List=(x y))) RHS=1)`},
171*4947cdc7SCole Faust		{`load("", "a", b="c")`,
172*4947cdc7SCole Faust			`(LoadStmt Module="" From=(a c) To=(a b))`},
173*4947cdc7SCole Faust		{`if True: load("", "a", b="c")`, // load needn't be at toplevel
174*4947cdc7SCole Faust			`(IfStmt Cond=True True=((LoadStmt Module="" From=(a c) To=(a b))))`},
175*4947cdc7SCole Faust		{`def f(x, *args, **kwargs):
176*4947cdc7SCole Faust	pass`,
177*4947cdc7SCole Faust			`(DefStmt Name=f Params=(x (UnaryExpr Op=* X=args) (UnaryExpr Op=** X=kwargs)) Body=((BranchStmt Token=pass)))`},
178*4947cdc7SCole Faust		{`def f(**kwargs, *args): pass`,
179*4947cdc7SCole Faust			`(DefStmt Name=f Params=((UnaryExpr Op=** X=kwargs) (UnaryExpr Op=* X=args)) Body=((BranchStmt Token=pass)))`},
180*4947cdc7SCole Faust		{`def f(a, b, c=d): pass`,
181*4947cdc7SCole Faust			`(DefStmt Name=f Params=(a b (BinaryExpr X=c Op== Y=d)) Body=((BranchStmt Token=pass)))`},
182*4947cdc7SCole Faust		{`def f(a, b=c, d): pass`,
183*4947cdc7SCole Faust			`(DefStmt Name=f Params=(a (BinaryExpr X=b Op== Y=c) d) Body=((BranchStmt Token=pass)))`}, // TODO(adonovan): fix this
184*4947cdc7SCole Faust		{`def f():
185*4947cdc7SCole Faust	def g():
186*4947cdc7SCole Faust		pass
187*4947cdc7SCole Faust	pass
188*4947cdc7SCole Faustdef h():
189*4947cdc7SCole Faust	pass`,
190*4947cdc7SCole Faust			`(DefStmt Name=f Body=((DefStmt Name=g Body=((BranchStmt Token=pass))) (BranchStmt Token=pass)))`},
191*4947cdc7SCole Faust		{"f();g()",
192*4947cdc7SCole Faust			`(ExprStmt X=(CallExpr Fn=f))`},
193*4947cdc7SCole Faust		{"f();",
194*4947cdc7SCole Faust			`(ExprStmt X=(CallExpr Fn=f))`},
195*4947cdc7SCole Faust		{"f();g()\n",
196*4947cdc7SCole Faust			`(ExprStmt X=(CallExpr Fn=f))`},
197*4947cdc7SCole Faust		{"f();\n",
198*4947cdc7SCole Faust			`(ExprStmt X=(CallExpr Fn=f))`},
199*4947cdc7SCole Faust	} {
200*4947cdc7SCole Faust		f, err := syntax.Parse("foo.star", test.input, 0)
201*4947cdc7SCole Faust		if err != nil {
202*4947cdc7SCole Faust			t.Errorf("parse `%s` failed: %v", test.input, stripPos(err))
203*4947cdc7SCole Faust			continue
204*4947cdc7SCole Faust		}
205*4947cdc7SCole Faust		if got := treeString(f.Stmts[0]); test.want != got {
206*4947cdc7SCole Faust			t.Errorf("parse `%s` = %s, want %s", test.input, got, test.want)
207*4947cdc7SCole Faust		}
208*4947cdc7SCole Faust	}
209*4947cdc7SCole Faust}
210*4947cdc7SCole Faust
211*4947cdc7SCole Faust// TestFileParseTrees tests sequences of statements, and particularly
212*4947cdc7SCole Faust// handling of indentation, newlines, line continuations, and blank lines.
213*4947cdc7SCole Faustfunc TestFileParseTrees(t *testing.T) {
214*4947cdc7SCole Faust	for _, test := range []struct {
215*4947cdc7SCole Faust		input, want string
216*4947cdc7SCole Faust	}{
217*4947cdc7SCole Faust		{`x = 1
218*4947cdc7SCole Faustprint(x)`,
219*4947cdc7SCole Faust			`(AssignStmt Op== LHS=x RHS=1)
220*4947cdc7SCole Faust(ExprStmt X=(CallExpr Fn=print Args=(x)))`},
221*4947cdc7SCole Faust		{"if cond:\n\tpass",
222*4947cdc7SCole Faust			`(IfStmt Cond=cond True=((BranchStmt Token=pass)))`},
223*4947cdc7SCole Faust		{"if cond:\n\tpass\nelse:\n\tpass",
224*4947cdc7SCole Faust			`(IfStmt Cond=cond True=((BranchStmt Token=pass)) False=((BranchStmt Token=pass)))`},
225*4947cdc7SCole Faust		{`def f():
226*4947cdc7SCole Faust	pass
227*4947cdc7SCole Faustpass
228*4947cdc7SCole Faust
229*4947cdc7SCole Faustpass`,
230*4947cdc7SCole Faust			`(DefStmt Name=f Body=((BranchStmt Token=pass)))
231*4947cdc7SCole Faust(BranchStmt Token=pass)
232*4947cdc7SCole Faust(BranchStmt Token=pass)`},
233*4947cdc7SCole Faust		{`pass; pass`,
234*4947cdc7SCole Faust			`(BranchStmt Token=pass)
235*4947cdc7SCole Faust(BranchStmt Token=pass)`},
236*4947cdc7SCole Faust		{"pass\npass",
237*4947cdc7SCole Faust			`(BranchStmt Token=pass)
238*4947cdc7SCole Faust(BranchStmt Token=pass)`},
239*4947cdc7SCole Faust		{"pass\n\npass",
240*4947cdc7SCole Faust			`(BranchStmt Token=pass)
241*4947cdc7SCole Faust(BranchStmt Token=pass)`},
242*4947cdc7SCole Faust		{`x = (1 +
243*4947cdc7SCole Faust2)`,
244*4947cdc7SCole Faust			`(AssignStmt Op== LHS=x RHS=(ParenExpr X=(BinaryExpr X=1 Op=+ Y=2)))`},
245*4947cdc7SCole Faust		{`x = 1 \
246*4947cdc7SCole Faust+ 2`,
247*4947cdc7SCole Faust			`(AssignStmt Op== LHS=x RHS=(BinaryExpr X=1 Op=+ Y=2))`},
248*4947cdc7SCole Faust	} {
249*4947cdc7SCole Faust		f, err := syntax.Parse("foo.star", test.input, 0)
250*4947cdc7SCole Faust		if err != nil {
251*4947cdc7SCole Faust			t.Errorf("parse `%s` failed: %v", test.input, stripPos(err))
252*4947cdc7SCole Faust			continue
253*4947cdc7SCole Faust		}
254*4947cdc7SCole Faust		var buf bytes.Buffer
255*4947cdc7SCole Faust		for i, stmt := range f.Stmts {
256*4947cdc7SCole Faust			if i > 0 {
257*4947cdc7SCole Faust				buf.WriteByte('\n')
258*4947cdc7SCole Faust			}
259*4947cdc7SCole Faust			writeTree(&buf, reflect.ValueOf(stmt))
260*4947cdc7SCole Faust		}
261*4947cdc7SCole Faust		if got := buf.String(); test.want != got {
262*4947cdc7SCole Faust			t.Errorf("parse `%s` = %s, want %s", test.input, got, test.want)
263*4947cdc7SCole Faust		}
264*4947cdc7SCole Faust	}
265*4947cdc7SCole Faust}
266*4947cdc7SCole Faust
267*4947cdc7SCole Faust// TestCompoundStmt tests handling of REPL-style compound statements.
268*4947cdc7SCole Faustfunc TestCompoundStmt(t *testing.T) {
269*4947cdc7SCole Faust	for _, test := range []struct {
270*4947cdc7SCole Faust		input, want string
271*4947cdc7SCole Faust	}{
272*4947cdc7SCole Faust		// blank lines
273*4947cdc7SCole Faust		{"\n",
274*4947cdc7SCole Faust			``},
275*4947cdc7SCole Faust		{"   \n",
276*4947cdc7SCole Faust			``},
277*4947cdc7SCole Faust		{"# comment\n",
278*4947cdc7SCole Faust			``},
279*4947cdc7SCole Faust		// simple statement
280*4947cdc7SCole Faust		{"1\n",
281*4947cdc7SCole Faust			`(ExprStmt X=1)`},
282*4947cdc7SCole Faust		{"print(1)\n",
283*4947cdc7SCole Faust			`(ExprStmt X=(CallExpr Fn=print Args=(1)))`},
284*4947cdc7SCole Faust		{"1;2;3;\n",
285*4947cdc7SCole Faust			`(ExprStmt X=1)(ExprStmt X=2)(ExprStmt X=3)`},
286*4947cdc7SCole Faust		{"f();g()\n",
287*4947cdc7SCole Faust			`(ExprStmt X=(CallExpr Fn=f))(ExprStmt X=(CallExpr Fn=g))`},
288*4947cdc7SCole Faust		{"f();\n",
289*4947cdc7SCole Faust			`(ExprStmt X=(CallExpr Fn=f))`},
290*4947cdc7SCole Faust		{"f(\n\n\n\n\n\n\n)\n",
291*4947cdc7SCole Faust			`(ExprStmt X=(CallExpr Fn=f))`},
292*4947cdc7SCole Faust		// complex statements
293*4947cdc7SCole Faust		{"def f():\n  pass\n\n",
294*4947cdc7SCole Faust			`(DefStmt Name=f Body=((BranchStmt Token=pass)))`},
295*4947cdc7SCole Faust		{"if cond:\n  pass\n\n",
296*4947cdc7SCole Faust			`(IfStmt Cond=cond True=((BranchStmt Token=pass)))`},
297*4947cdc7SCole Faust		// Even as a 1-liner, the following blank line is required.
298*4947cdc7SCole Faust		{"if cond: pass\n\n",
299*4947cdc7SCole Faust			`(IfStmt Cond=cond True=((BranchStmt Token=pass)))`},
300*4947cdc7SCole Faust		// github.com/google/starlark-go/issues/121
301*4947cdc7SCole Faust		{"a; b; c\n",
302*4947cdc7SCole Faust			`(ExprStmt X=a)(ExprStmt X=b)(ExprStmt X=c)`},
303*4947cdc7SCole Faust		{"a; b c\n",
304*4947cdc7SCole Faust			`invalid syntax`},
305*4947cdc7SCole Faust	} {
306*4947cdc7SCole Faust
307*4947cdc7SCole Faust		// Fake readline input from string.
308*4947cdc7SCole Faust		// The ! suffix, which would cause a parse error,
309*4947cdc7SCole Faust		// tests that the parser doesn't read more than necessary.
310*4947cdc7SCole Faust		sc := bufio.NewScanner(strings.NewReader(test.input + "!"))
311*4947cdc7SCole Faust		readline := func() ([]byte, error) {
312*4947cdc7SCole Faust			if sc.Scan() {
313*4947cdc7SCole Faust				return []byte(sc.Text() + "\n"), nil
314*4947cdc7SCole Faust			}
315*4947cdc7SCole Faust			return nil, sc.Err()
316*4947cdc7SCole Faust		}
317*4947cdc7SCole Faust
318*4947cdc7SCole Faust		var got string
319*4947cdc7SCole Faust		f, err := syntax.ParseCompoundStmt("foo.star", readline)
320*4947cdc7SCole Faust		if err != nil {
321*4947cdc7SCole Faust			got = stripPos(err)
322*4947cdc7SCole Faust		} else {
323*4947cdc7SCole Faust			for _, stmt := range f.Stmts {
324*4947cdc7SCole Faust				got += treeString(stmt)
325*4947cdc7SCole Faust			}
326*4947cdc7SCole Faust		}
327*4947cdc7SCole Faust		if test.want != got {
328*4947cdc7SCole Faust			t.Errorf("parse `%s` = %s, want %s", test.input, got, test.want)
329*4947cdc7SCole Faust		}
330*4947cdc7SCole Faust	}
331*4947cdc7SCole Faust}
332*4947cdc7SCole Faust
333*4947cdc7SCole Faustfunc stripPos(err error) string {
334*4947cdc7SCole Faust	s := err.Error()
335*4947cdc7SCole Faust	if i := strings.Index(s, ": "); i >= 0 {
336*4947cdc7SCole Faust		s = s[i+len(": "):] // strip file:line:col
337*4947cdc7SCole Faust	}
338*4947cdc7SCole Faust	return s
339*4947cdc7SCole Faust}
340*4947cdc7SCole Faust
341*4947cdc7SCole Faust// treeString prints a syntax node as a parenthesized tree.
342*4947cdc7SCole Faust// Idents are printed as foo and Literals as "foo" or 42.
343*4947cdc7SCole Faust// Structs are printed as (type name=value ...).
344*4947cdc7SCole Faust// Only non-empty fields are shown.
345*4947cdc7SCole Faustfunc treeString(n syntax.Node) string {
346*4947cdc7SCole Faust	var buf bytes.Buffer
347*4947cdc7SCole Faust	writeTree(&buf, reflect.ValueOf(n))
348*4947cdc7SCole Faust	return buf.String()
349*4947cdc7SCole Faust}
350*4947cdc7SCole Faust
351*4947cdc7SCole Faustfunc writeTree(out *bytes.Buffer, x reflect.Value) {
352*4947cdc7SCole Faust	switch x.Kind() {
353*4947cdc7SCole Faust	case reflect.String, reflect.Int, reflect.Bool:
354*4947cdc7SCole Faust		fmt.Fprintf(out, "%v", x.Interface())
355*4947cdc7SCole Faust	case reflect.Ptr, reflect.Interface:
356*4947cdc7SCole Faust		if elem := x.Elem(); elem.Kind() == 0 {
357*4947cdc7SCole Faust			out.WriteString("nil")
358*4947cdc7SCole Faust		} else {
359*4947cdc7SCole Faust			writeTree(out, elem)
360*4947cdc7SCole Faust		}
361*4947cdc7SCole Faust	case reflect.Struct:
362*4947cdc7SCole Faust		switch v := x.Interface().(type) {
363*4947cdc7SCole Faust		case syntax.Literal:
364*4947cdc7SCole Faust			switch v.Token {
365*4947cdc7SCole Faust			case syntax.STRING:
366*4947cdc7SCole Faust				fmt.Fprintf(out, "%q", v.Value)
367*4947cdc7SCole Faust			case syntax.BYTES:
368*4947cdc7SCole Faust				fmt.Fprintf(out, "b%q", v.Value)
369*4947cdc7SCole Faust			case syntax.INT:
370*4947cdc7SCole Faust				fmt.Fprintf(out, "%d", v.Value)
371*4947cdc7SCole Faust			}
372*4947cdc7SCole Faust			return
373*4947cdc7SCole Faust		case syntax.Ident:
374*4947cdc7SCole Faust			out.WriteString(v.Name)
375*4947cdc7SCole Faust			return
376*4947cdc7SCole Faust		}
377*4947cdc7SCole Faust		fmt.Fprintf(out, "(%s", strings.TrimPrefix(x.Type().String(), "syntax."))
378*4947cdc7SCole Faust		for i, n := 0, x.NumField(); i < n; i++ {
379*4947cdc7SCole Faust			f := x.Field(i)
380*4947cdc7SCole Faust			if f.Type() == reflect.TypeOf(syntax.Position{}) {
381*4947cdc7SCole Faust				continue // skip positions
382*4947cdc7SCole Faust			}
383*4947cdc7SCole Faust			name := x.Type().Field(i).Name
384*4947cdc7SCole Faust			if name == "commentsRef" {
385*4947cdc7SCole Faust				continue // skip comments fields
386*4947cdc7SCole Faust			}
387*4947cdc7SCole Faust			if f.Type() == reflect.TypeOf(syntax.Token(0)) {
388*4947cdc7SCole Faust				fmt.Fprintf(out, " %s=%s", name, f.Interface())
389*4947cdc7SCole Faust				continue
390*4947cdc7SCole Faust			}
391*4947cdc7SCole Faust
392*4947cdc7SCole Faust			switch f.Kind() {
393*4947cdc7SCole Faust			case reflect.Slice:
394*4947cdc7SCole Faust				if n := f.Len(); n > 0 {
395*4947cdc7SCole Faust					fmt.Fprintf(out, " %s=(", name)
396*4947cdc7SCole Faust					for i := 0; i < n; i++ {
397*4947cdc7SCole Faust						if i > 0 {
398*4947cdc7SCole Faust							out.WriteByte(' ')
399*4947cdc7SCole Faust						}
400*4947cdc7SCole Faust						writeTree(out, f.Index(i))
401*4947cdc7SCole Faust					}
402*4947cdc7SCole Faust					out.WriteByte(')')
403*4947cdc7SCole Faust				}
404*4947cdc7SCole Faust				continue
405*4947cdc7SCole Faust			case reflect.Ptr, reflect.Interface:
406*4947cdc7SCole Faust				if f.IsNil() {
407*4947cdc7SCole Faust					continue
408*4947cdc7SCole Faust				}
409*4947cdc7SCole Faust			case reflect.Int:
410*4947cdc7SCole Faust				if f.Int() != 0 {
411*4947cdc7SCole Faust					fmt.Fprintf(out, " %s=%d", name, f.Int())
412*4947cdc7SCole Faust				}
413*4947cdc7SCole Faust				continue
414*4947cdc7SCole Faust			case reflect.Bool:
415*4947cdc7SCole Faust				if f.Bool() {
416*4947cdc7SCole Faust					fmt.Fprintf(out, " %s", name)
417*4947cdc7SCole Faust				}
418*4947cdc7SCole Faust				continue
419*4947cdc7SCole Faust			}
420*4947cdc7SCole Faust			fmt.Fprintf(out, " %s=", name)
421*4947cdc7SCole Faust			writeTree(out, f)
422*4947cdc7SCole Faust		}
423*4947cdc7SCole Faust		fmt.Fprintf(out, ")")
424*4947cdc7SCole Faust	default:
425*4947cdc7SCole Faust		fmt.Fprintf(out, "%T", x.Interface())
426*4947cdc7SCole Faust	}
427*4947cdc7SCole Faust}
428*4947cdc7SCole Faust
429*4947cdc7SCole Faustfunc TestParseErrors(t *testing.T) {
430*4947cdc7SCole Faust	filename := starlarktest.DataFile("syntax", "testdata/errors.star")
431*4947cdc7SCole Faust	for _, chunk := range chunkedfile.Read(filename, t) {
432*4947cdc7SCole Faust		_, err := syntax.Parse(filename, chunk.Source, 0)
433*4947cdc7SCole Faust		switch err := err.(type) {
434*4947cdc7SCole Faust		case nil:
435*4947cdc7SCole Faust			// ok
436*4947cdc7SCole Faust		case syntax.Error:
437*4947cdc7SCole Faust			chunk.GotError(int(err.Pos.Line), err.Msg)
438*4947cdc7SCole Faust		default:
439*4947cdc7SCole Faust			t.Error(err)
440*4947cdc7SCole Faust		}
441*4947cdc7SCole Faust		chunk.Done()
442*4947cdc7SCole Faust	}
443*4947cdc7SCole Faust}
444*4947cdc7SCole Faust
445*4947cdc7SCole Faustfunc TestFilePortion(t *testing.T) {
446*4947cdc7SCole Faust	// Imagine that the Starlark file or expression print(x.f) is extracted
447*4947cdc7SCole Faust	// from the middle of a file in some hypothetical template language;
448*4947cdc7SCole Faust	// see https://github.com/google/starlark-go/issues/346. For example:
449*4947cdc7SCole Faust	// --
450*4947cdc7SCole Faust	// {{loop x seq}}
451*4947cdc7SCole Faust	//   {{print(x.f)}}
452*4947cdc7SCole Faust	// {{end}}
453*4947cdc7SCole Faust	// --
454*4947cdc7SCole Faust	fp := syntax.FilePortion{Content: []byte("print(x.f)"), FirstLine: 2, FirstCol: 4}
455*4947cdc7SCole Faust	file, err := syntax.Parse("foo.template", fp, 0)
456*4947cdc7SCole Faust	if err != nil {
457*4947cdc7SCole Faust		t.Fatal(err)
458*4947cdc7SCole Faust	}
459*4947cdc7SCole Faust	span := fmt.Sprint(file.Stmts[0].Span())
460*4947cdc7SCole Faust	want := "foo.template:2:4 foo.template:2:14"
461*4947cdc7SCole Faust	if span != want {
462*4947cdc7SCole Faust		t.Errorf("wrong span: got %q, want %q", span, want)
463*4947cdc7SCole Faust	}
464*4947cdc7SCole Faust}
465*4947cdc7SCole Faust
466*4947cdc7SCole Faust// dataFile is the same as starlarktest.DataFile.
467*4947cdc7SCole Faust// We make a copy to avoid a dependency cycle.
468*4947cdc7SCole Faustvar dataFile = func(pkgdir, filename string) string {
469*4947cdc7SCole Faust	return filepath.Join(build.Default.GOPATH, "src/go.starlark.net", pkgdir, filename)
470*4947cdc7SCole Faust}
471*4947cdc7SCole Faust
472*4947cdc7SCole Faustfunc BenchmarkParse(b *testing.B) {
473*4947cdc7SCole Faust	filename := dataFile("syntax", "testdata/scan.star")
474*4947cdc7SCole Faust	b.StopTimer()
475*4947cdc7SCole Faust	data, err := ioutil.ReadFile(filename)
476*4947cdc7SCole Faust	if err != nil {
477*4947cdc7SCole Faust		b.Fatal(err)
478*4947cdc7SCole Faust	}
479*4947cdc7SCole Faust	b.StartTimer()
480*4947cdc7SCole Faust
481*4947cdc7SCole Faust	for i := 0; i < b.N; i++ {
482*4947cdc7SCole Faust		_, err := syntax.Parse(filename, data, 0)
483*4947cdc7SCole Faust		if err != nil {
484*4947cdc7SCole Faust			b.Fatal(err)
485*4947cdc7SCole Faust		}
486*4947cdc7SCole Faust	}
487*4947cdc7SCole Faust}
488