github.com/lab47/exprcore@v0.0.0-20210525052339-fb7d6bd9331e/syntax/parse_test.go (about)

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