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