github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/parse/parse_test.go (about)

     1  package parse
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"testing"
     7  )
     8  
     9  func a(c ...interface{}) ast {
    10  	// Shorthand used for checking Compound and levels beneath.
    11  	return ast{"Chunk/Pipeline/Form", fs{"Head": "a", "Args": c}}
    12  }
    13  
    14  var testCases = []struct {
    15  	name string
    16  	code string
    17  	node Node
    18  	want ast
    19  
    20  	wantErrPart  string
    21  	wantErrAtEnd bool
    22  	wantErrMsg   string
    23  }{
    24  	// Chunk
    25  	{
    26  		name: "empty chunk",
    27  		code: "",
    28  		node: &Chunk{},
    29  		want: ast{"Chunk", fs{"Pipelines": nil}},
    30  	},
    31  	{
    32  		name: "multiple pipelines separated by newlines and semicolons",
    33  		code: "a;b;c\n;d",
    34  		node: &Chunk{},
    35  		want: ast{"Chunk", fs{"Pipelines": []string{"a", "b", "c", "d"}}},
    36  	},
    37  	{
    38  		name: "extra newlines and semicolons do not result in empty pipelines",
    39  		code: "  ;\n\n  ls \t ;\n",
    40  		node: &Chunk{},
    41  		want: ast{"Chunk", fs{"Pipelines": []string{"ls \t "}}},
    42  	},
    43  
    44  	// Pipeline
    45  	{
    46  		name: "pipeline",
    47  		code: "a|b|c|d",
    48  		node: &Pipeline{},
    49  		want: ast{"Pipeline", fs{"Forms": []string{"a", "b", "c", "d"}}},
    50  	},
    51  	{
    52  		name: "newlines after pipes are allowed",
    53  		code: "a| \n \n b",
    54  		node: &Pipeline{},
    55  		want: ast{"Pipeline", fs{"Forms": []string{"a", "b"}}},
    56  	},
    57  
    58  	{
    59  		name:         "no form after pipe",
    60  		code:         "a|",
    61  		node:         &Chunk{},
    62  		wantErrAtEnd: true,
    63  		wantErrMsg:   "should be form",
    64  	},
    65  
    66  	// Form
    67  	{
    68  		name: "command form",
    69  		code: "ls x y",
    70  		node: &Form{},
    71  		want: ast{"Form", fs{
    72  			"Head": "ls",
    73  			"Args": []string{"x", "y"}}},
    74  	},
    75  	{
    76  		name: "assignment form",
    77  		code: "k=v k[a][b]=v {a,b[1]}=(ha)",
    78  		node: &Form{},
    79  		want: ast{"Form", fs{
    80  			"Assignments": []string{"k=v", "k[a][b]=v", "{a,b[1]}=(ha)"}}},
    81  	},
    82  	{
    83  		name: "temporary assignment",
    84  		code: "k=v k[a][b]=v a",
    85  		node: &Form{},
    86  		want: ast{"Form", fs{
    87  			"Assignments": []string{"k=v", "k[a][b]=v"},
    88  			"Head":        "a"}},
    89  	},
    90  	{
    91  		name: "redirection",
    92  		code: "a >b",
    93  		node: &Form{},
    94  		want: ast{"Form", fs{
    95  			"Head": "a",
    96  			"Redirs": []ast{
    97  				{"Redir", fs{"Mode": Write, "Right": "b"}}},
    98  		}},
    99  	},
   100  	{
   101  		name: "advanced redirections",
   102  		code: "a >>b 2>b 3>&- 4>&1 5<c 6<>d",
   103  		node: &Form{},
   104  		want: ast{"Form", fs{
   105  			"Head": "a",
   106  			"Redirs": []ast{
   107  				{"Redir", fs{"Mode": Append, "Right": "b"}},
   108  				{"Redir", fs{"Left": "2", "Mode": Write, "Right": "b"}},
   109  				{"Redir", fs{"Left": "3", "Mode": Write, "RightIsFd": true, "Right": "-"}},
   110  				{"Redir", fs{"Left": "4", "Mode": Write, "RightIsFd": true, "Right": "1"}},
   111  				{"Redir", fs{"Left": "5", "Mode": Read, "Right": "c"}},
   112  				{"Redir", fs{"Left": "6", "Mode": ReadWrite, "Right": "d"}},
   113  			},
   114  		}}},
   115  	{
   116  		name: "command options",
   117  		code: "a &a=1 x &b=2",
   118  		node: &Form{},
   119  		want: ast{"Form", fs{
   120  			"Head": "a",
   121  			"Args": []string{"x"},
   122  			"Opts": []string{"&a=1", "&b=2"},
   123  		}},
   124  		// More tests for MapPair below with map syntax
   125  	},
   126  
   127  	{
   128  		name:        "bogus ampersand in command form",
   129  		code:        "a & &",
   130  		node:        &Chunk{},
   131  		wantErrPart: "&",
   132  		wantErrMsg:  "unexpected rune '&'",
   133  	},
   134  	{
   135  		name:         "no filename redirection source",
   136  		code:         "a >",
   137  		node:         &Chunk{},
   138  		wantErrAtEnd: true,
   139  		wantErrMsg:   "should be a composite term representing filename",
   140  	},
   141  	{
   142  		name:         "no FD direction source",
   143  		code:         "a >&",
   144  		node:         &Chunk{},
   145  		wantErrAtEnd: true,
   146  		wantErrMsg:   "should be a composite term representing fd",
   147  	},
   148  
   149  	// Filter
   150  	{
   151  		name: "empty filter",
   152  		code: "",
   153  		node: &Filter{},
   154  		want: ast{"Filter", fs{}},
   155  	},
   156  	{
   157  		name: "filter with arguments",
   158  		code: "foo bar",
   159  		node: &Filter{},
   160  		want: ast{"Filter", fs{"Args": []string{"foo", "bar"}}},
   161  	},
   162  	{
   163  		name: "filter with options",
   164  		code: "&foo=bar &lorem=ipsum",
   165  		node: &Filter{},
   166  		want: ast{"Filter", fs{"Opts": []string{"&foo=bar", "&lorem=ipsum"}}},
   167  	},
   168  	{
   169  		name: "filter mixing arguments and options",
   170  		code: "foo &a=b bar &x=y",
   171  		node: &Filter{},
   172  		want: ast{"Filter", fs{
   173  			"Args": []string{"foo", "bar"},
   174  			"Opts": []string{"&a=b", "&x=y"}}},
   175  	},
   176  	{
   177  		name: "filter with leading and trailing whitespaces",
   178  		code: "  foo  ",
   179  		node: &Filter{},
   180  		want: ast{"Filter", fs{"Args": []string{"foo"}}},
   181  	},
   182  
   183  	// Compound
   184  	{
   185  		name: "compound expression",
   186  		code: `b"foo"?$c*'xyz'`,
   187  		node: &Compound{},
   188  		want: ast{"Compound", fs{
   189  			"Indexings": []string{"b", `"foo"`, "?", "$c", "*", "'xyz'"}}},
   190  	},
   191  
   192  	// Indexing
   193  	{
   194  		name: "indexing expression",
   195  		code: "$b[c][d][\ne\n]",
   196  		node: &Indexing{},
   197  		want: ast{"Indexing", fs{
   198  			"Head": "$b", "Indices": []string{"c", "d", "\ne\n"}}},
   199  	},
   200  
   201  	// Primary
   202  	{
   203  		name: "bareword",
   204  		code: "foo",
   205  		node: &Primary{},
   206  		want: ast{"Primary", fs{"Type": Bareword, "Value": "foo"}},
   207  	},
   208  	{
   209  		name: "bareword with all allowed symbols",
   210  		code: "./\\@%+!=,",
   211  		node: &Primary{},
   212  		want: ast{"Primary", fs{"Type": Bareword, "Value": "./\\@%+!=,"}},
   213  	},
   214  	{
   215  		name: "single-quoted string",
   216  		code: "'''x''y'''",
   217  		node: &Primary{},
   218  		want: ast{"Primary", fs{"Type": SingleQuoted, "Value": "'x'y'"}},
   219  	},
   220  	{
   221  		name: "double-quoted string with control char escape sequences",
   222  		code: `"[\c?\c@\cI\^I\^[]"`,
   223  		node: &Primary{},
   224  		want: ast{"Primary", fs{
   225  			"Type":  DoubleQuoted,
   226  			"Value": "[\x7f\x00\t\t\x1b]",
   227  		}},
   228  	},
   229  	{
   230  		name: "double-quoted string with single-char escape sequences",
   231  		code: `"[\n\t\a\v\\\"]"`,
   232  		node: &Primary{},
   233  		want: ast{"Primary", fs{
   234  			"Type":  DoubleQuoted,
   235  			"Value": "[\n\t\a\v\\\"]",
   236  		}},
   237  	},
   238  	{
   239  		name: "double-quoted string with numerical escape sequences for codepoints",
   240  		code: `"b\^[\u548c\U0002CE23\n\t\\"`,
   241  		node: &Primary{},
   242  		want: ast{"Primary", fs{
   243  			"Type":  DoubleQuoted,
   244  			"Value": "b\x1b\u548c\U0002CE23\n\t\\",
   245  		}},
   246  	},
   247  	{
   248  		name: "double-quoted string with numerical escape sequences for bytes",
   249  		code: `"\123\321 \x7f\xff"`,
   250  		node: &Primary{},
   251  		want: ast{"Primary", fs{
   252  			"Type":  DoubleQuoted,
   253  			"Value": "\123\321 \x7f\xff",
   254  		}},
   255  	},
   256  	{
   257  		name: "wildcard",
   258  		code: "a * ? ** ??",
   259  		node: &Chunk{},
   260  		want: a(
   261  			ast{"Compound/Indexing/Primary", fs{"Type": Wildcard, "Value": "*"}},
   262  			ast{"Compound/Indexing/Primary", fs{"Type": Wildcard, "Value": "?"}},
   263  			ast{"Compound/Indexing/Primary", fs{"Type": Wildcard, "Value": "**"}},
   264  			ast{"Compound", fs{"Indexings": []string{"?", "?"}}},
   265  		),
   266  	},
   267  	{
   268  		name: "variable",
   269  		code: `a $x $'!@#' $"\n"`,
   270  		node: &Chunk{},
   271  		want: a(
   272  			ast{"Compound/Indexing/Primary", fs{"Type": Variable, "Value": "x"}},
   273  			ast{"Compound/Indexing/Primary", fs{"Type": Variable, "Value": "!@#"}},
   274  			ast{"Compound/Indexing/Primary", fs{"Type": Variable, "Value": "\n"}},
   275  		),
   276  	},
   277  	{
   278  		name: "list",
   279  		code: "a [] [ ] [1] [ 2] [3 ] [\n 4 \n5\n 6 7 \n]",
   280  		node: &Chunk{},
   281  		want: a(
   282  			ast{"Compound/Indexing/Primary", fs{
   283  				"Type":     List,
   284  				"Elements": []ast{}}},
   285  			ast{"Compound/Indexing/Primary", fs{
   286  				"Type":     List,
   287  				"Elements": []ast{}}},
   288  			ast{"Compound/Indexing/Primary", fs{
   289  				"Type":     List,
   290  				"Elements": []string{"1"}}},
   291  			ast{"Compound/Indexing/Primary", fs{
   292  				"Type":     List,
   293  				"Elements": []string{"2"}}},
   294  			ast{"Compound/Indexing/Primary", fs{
   295  				"Type":     List,
   296  				"Elements": []string{"3"}}},
   297  			ast{"Compound/Indexing/Primary", fs{
   298  				"Type":     List,
   299  				"Elements": []string{"4", "5", "6", "7"}}},
   300  		),
   301  	},
   302  	{
   303  		name: "map",
   304  		code: "a [&k=v] [ &k=v] [&k=v ] [ &k=v ] [ &k= v] [&k= \n v] [\n&a=b &c=d \n &e=f\n\n]",
   305  		node: &Chunk{},
   306  		want: a(
   307  			ast{"Compound/Indexing/Primary", fs{
   308  				"Type":     Map,
   309  				"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
   310  			ast{"Compound/Indexing/Primary", fs{
   311  				"Type":     Map,
   312  				"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
   313  			ast{"Compound/Indexing/Primary", fs{
   314  				"Type":     Map,
   315  				"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
   316  			ast{"Compound/Indexing/Primary", fs{
   317  				"Type":     Map,
   318  				"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
   319  			ast{"Compound/Indexing/Primary", fs{
   320  				"Type":     Map,
   321  				"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
   322  			ast{"Compound/Indexing/Primary", fs{
   323  				"Type":     Map,
   324  				"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
   325  			ast{"Compound/Indexing/Primary", fs{
   326  				"Type": Map,
   327  				"MapPairs": []ast{
   328  					{"MapPair", fs{"Key": "a", "Value": "b"}},
   329  					{"MapPair", fs{"Key": "c", "Value": "d"}},
   330  					{"MapPair", fs{"Key": "e", "Value": "f"}},
   331  				}}},
   332  		),
   333  	},
   334  	{
   335  		name: "empty map",
   336  		code: "a [&] [ &] [& ] [ & ]",
   337  		node: &Chunk{},
   338  		want: a(
   339  			ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
   340  			ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
   341  			ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
   342  			ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
   343  		),
   344  	},
   345  	{
   346  		name: "lambda without signature",
   347  		code: "{ echo}",
   348  		node: &Primary{},
   349  		want: ast{"Primary", fs{
   350  			"Type":  Lambda,
   351  			"Chunk": "echo",
   352  		}},
   353  	},
   354  	{
   355  		name: "new-style lambda with arguments and options",
   356  		code: "{|a b &k=v| echo}",
   357  		node: &Primary{},
   358  		want: ast{"Primary", fs{
   359  			"Type":     Lambda,
   360  			"Elements": []string{"a", "b"},
   361  			"MapPairs": []string{"&k=v"},
   362  			"Chunk":    " echo",
   363  		}},
   364  	},
   365  	{
   366  		name: "output capture",
   367  		code: "a () (b;c) (c\nd)",
   368  		node: &Chunk{},
   369  		want: a(
   370  			ast{"Compound/Indexing/Primary", fs{
   371  				"Type": OutputCapture, "Chunk": ""}},
   372  			ast{"Compound/Indexing/Primary", fs{
   373  				"Type": OutputCapture, "Chunk": ast{
   374  					"Chunk", fs{"Pipelines": []string{"b", "c"}},
   375  				}}},
   376  			ast{"Compound/Indexing/Primary", fs{
   377  				"Type": OutputCapture, "Chunk": ast{
   378  					"Chunk", fs{"Pipelines": []string{"c", "d"}},
   379  				}}},
   380  		),
   381  	},
   382  	{
   383  		name: "exception capture",
   384  		code: "a ?() ?(b;c)",
   385  		node: &Chunk{},
   386  		want: a(
   387  			ast{"Compound/Indexing/Primary", fs{
   388  				"Type": ExceptionCapture, "Chunk": ""}},
   389  			ast{"Compound/Indexing/Primary", fs{
   390  				"Type": ExceptionCapture, "Chunk": "b;c",
   391  			}}),
   392  	},
   393  	{
   394  		name: "braced list",
   395  		code: "{,a,c\ng\n}",
   396  		node: &Primary{},
   397  		want: ast{"Primary", fs{
   398  			"Type":   Braced,
   399  			"Braced": []string{"", "a", "c", "g", ""}}},
   400  	},
   401  	{
   402  		name: "tilde",
   403  		code: "~xiaq/go",
   404  		node: &Compound{},
   405  		want: ast{"Compound", fs{
   406  			"Indexings": []ast{
   407  				{"Indexing/Primary", fs{"Type": Tilde, "Value": "~"}},
   408  				{"Indexing/Primary", fs{"Type": Bareword, "Value": "xiaq/go"}},
   409  			},
   410  		}},
   411  	},
   412  	{
   413  		name: "tilde and wildcard",
   414  		code: "~xiaq/*.go",
   415  		node: &Compound{},
   416  		want: ast{"Compound", fs{
   417  			"Indexings": []ast{
   418  				{"Indexing/Primary", fs{"Type": Tilde, "Value": "~"}},
   419  				{"Indexing/Primary", fs{"Type": Bareword, "Value": "xiaq/"}},
   420  				{"Indexing/Primary", fs{"Type": Wildcard, "Value": "*"}},
   421  				{"Indexing/Primary", fs{"Type": Bareword, "Value": ".go"}},
   422  			},
   423  		}},
   424  	},
   425  
   426  	{
   427  		name:         "unterminated single-quoted string",
   428  		code:         "'a",
   429  		node:         &Chunk{},
   430  		wantErrAtEnd: true,
   431  		wantErrMsg:   "string not terminated",
   432  	},
   433  	{
   434  		name:         "unterminated double-quoted string",
   435  		code:         `"a`,
   436  		node:         &Chunk{},
   437  		wantErrAtEnd: true,
   438  		wantErrMsg:   "string not terminated",
   439  	},
   440  	{
   441  		name:        "invalid control sequence",
   442  		code:        `a "\^` + "\t",
   443  		node:        &Chunk{},
   444  		wantErrPart: "\t",
   445  		wantErrMsg:  "invalid control sequence, should be a codepoint between 0x3F and 0x5F",
   446  	},
   447  	{
   448  		name:        "invalid hex escape sequence",
   449  		code:        `a "\xQQ"`,
   450  		node:        &Chunk{},
   451  		wantErrPart: "Q",
   452  		wantErrMsg:  "invalid escape sequence, should be hex digit",
   453  	},
   454  	{
   455  		name:        "invalid octal escape sequence",
   456  		code:        `a "\1ab"`,
   457  		node:        &Chunk{},
   458  		wantErrPart: "a",
   459  		wantErrMsg:  "invalid escape sequence, should be octal digit",
   460  	},
   461  	{
   462  		name:        "overflow in octal escape sequence",
   463  		code:        `a "\400"`,
   464  		node:        &Chunk{},
   465  		wantErrPart: "\\400",
   466  		wantErrMsg:  "invalid octal escape sequence, should be below 256",
   467  	},
   468  	{
   469  		name:        "invalid single-char escape sequence",
   470  		code:        `a "\i"`,
   471  		node:        &Chunk{},
   472  		wantErrPart: "i",
   473  		wantErrMsg:  "invalid escape sequence",
   474  	},
   475  	{
   476  		name:         "unterminated variable name",
   477  		code:         "$",
   478  		node:         &Chunk{},
   479  		wantErrAtEnd: true,
   480  		wantErrMsg:   "should be variable name",
   481  	},
   482  	{
   483  		name: "list-map hybrid not supported",
   484  		code: "a [a &k=v]",
   485  		node: &Chunk{},
   486  		// TODO(xiaq): Add correct position information.
   487  		wantErrAtEnd: true,
   488  		wantErrMsg:   "cannot contain both list elements and map pairs",
   489  	},
   490  
   491  	// Line continuation
   492  	{
   493  		name: "line continuation",
   494  		code: "a b^\nc",
   495  		node: &Chunk{},
   496  		want: ast{
   497  			"Chunk/Pipeline/Form", fs{"Head": "a", "Args": []string{"b", "c"}}},
   498  	},
   499  	{
   500  		name:         "unterminated line continuation",
   501  		code:         `a ^`,
   502  		node:         &Chunk{},
   503  		wantErrAtEnd: true,
   504  		wantErrMsg:   "should be newline",
   505  	},
   506  
   507  	// Carriage return
   508  	{
   509  		name: "carriage return separating pipelines",
   510  		code: "a\rb",
   511  		node: &Chunk{},
   512  		want: ast{"Chunk", fs{"Pipelines": []string{"a", "b"}}},
   513  	},
   514  	{
   515  		name: "carriage return + newline separating pipelines",
   516  		code: "a\r\nb",
   517  		node: &Chunk{},
   518  		want: ast{"Chunk", fs{"Pipelines": []string{"a", "b"}}},
   519  	},
   520  	{
   521  		name: "carriage return as whitespace padding in lambdas",
   522  		code: "a { \rfoo\r\nbar }",
   523  		node: &Chunk{},
   524  		want: a(
   525  			ast{"Compound/Indexing/Primary",
   526  				fs{"Type": Lambda, "Chunk": "foo\r\nbar "}},
   527  		),
   528  	},
   529  	{
   530  		name: "carriage return separating elements in a lists",
   531  		code: "a [a\rb]",
   532  		node: &Chunk{},
   533  		want: a(
   534  			ast{"Compound/Indexing/Primary", fs{
   535  				"Type":     List,
   536  				"Elements": []string{"a", "b"}}}),
   537  	},
   538  	{
   539  		name: "carriage return in line continuation",
   540  		code: "a b^\rc",
   541  		node: &Chunk{},
   542  		want: ast{
   543  			"Chunk/Pipeline/Form", fs{"Head": "a", "Args": []string{"b", "c"}}},
   544  	},
   545  	{
   546  		name: "carriage return + newline as a single newline in line continuation",
   547  		code: "a b^\r\nc",
   548  		node: &Chunk{},
   549  		want: ast{
   550  			"Chunk/Pipeline/Form", fs{"Head": "a", "Args": []string{"b", "c"}}},
   551  	},
   552  
   553  	// Comment
   554  	{
   555  		name: "comments in chunks",
   556  		code: "a#haha\nb#lala",
   557  		node: &Chunk{},
   558  		want: ast{
   559  			"Chunk", fs{"Pipelines": []ast{
   560  				{"Pipeline/Form", fs{"Head": "a"}},
   561  				{"Pipeline/Form", fs{"Head": "b"}},
   562  			}}},
   563  	},
   564  	{
   565  		name: "comments in lists",
   566  		code: "a [a#haha\nb]",
   567  		node: &Chunk{},
   568  		want: a(
   569  			ast{"Compound/Indexing/Primary", fs{
   570  				"Type":     List,
   571  				"Elements": []string{"a", "b"},
   572  			}},
   573  		),
   574  	},
   575  
   576  	// Other errors
   577  	{
   578  		name:        "unmatched )",
   579  		code:        ")",
   580  		node:        &Chunk{},
   581  		wantErrPart: ")",
   582  		wantErrMsg:  "unexpected rune ')'",
   583  	},
   584  	{
   585  		name:        "unmatched ]",
   586  		code:        "]",
   587  		node:        &Chunk{},
   588  		wantErrPart: "]",
   589  		wantErrMsg:  "unexpected rune ']'",
   590  	},
   591  	{
   592  		name:        "unmatched }",
   593  		code:        "}",
   594  		node:        &Chunk{},
   595  		wantErrPart: "}",
   596  		wantErrMsg:  "unexpected rune '}'",
   597  	},
   598  	{
   599  		name:         "unmatched (",
   600  		code:         "a (",
   601  		node:         &Chunk{},
   602  		wantErrAtEnd: true,
   603  		wantErrMsg:   "should be ')'",
   604  	},
   605  	{
   606  		name:         "unmatched [",
   607  		code:         "a [",
   608  		node:         &Chunk{},
   609  		wantErrAtEnd: true,
   610  		wantErrMsg:   "should be ']'",
   611  	},
   612  	{
   613  		name:         "unmatched {",
   614  		code:         "a {",
   615  		node:         &Chunk{},
   616  		wantErrAtEnd: true,
   617  		wantErrMsg:   "should be ',' or '}'",
   618  	},
   619  	{
   620  		name:         "unmatched { in lambda",
   621  		code:         "a { ",
   622  		node:         &Chunk{},
   623  		wantErrAtEnd: true,
   624  		wantErrMsg:   "should be '}'",
   625  	},
   626  	{
   627  		name:        "unmatched [ in indexing expression",
   628  		code:        "a $a[0}",
   629  		node:        &Chunk{},
   630  		wantErrPart: "}",
   631  		wantErrMsg:  "should be ']'",
   632  	},
   633  }
   634  
   635  func TestParse(t *testing.T) {
   636  	for _, test := range testCases {
   637  		t.Run(test.name, func(t *testing.T) {
   638  			n := test.node
   639  			src := SourceForTest(test.code)
   640  			err := ParseAs(src, n, Config{})
   641  			if test.wantErrMsg == "" {
   642  				if err != nil {
   643  					t.Errorf("Parse(%q) returns error: %v", test.code, err)
   644  				}
   645  				err = checkParseTree(n)
   646  				if err != nil {
   647  					t.Errorf("Parse(%q) returns bad parse tree: %v", test.code, err)
   648  					fmt.Fprintf(os.Stderr, "Parse tree of %q:\n", test.code)
   649  					pprintParseTree(n, os.Stderr)
   650  				}
   651  				err = checkAST(n, test.want)
   652  				if err != nil {
   653  					t.Errorf("Parse(%q) returns bad AST: %v", test.code, err)
   654  					fmt.Fprintf(os.Stderr, "AST of %q:\n", test.code)
   655  					pprintAST(n, os.Stderr)
   656  				}
   657  			} else {
   658  				if err == nil {
   659  					t.Errorf("Parse(%q) returns no error, want error with %q",
   660  						test.code, test.wantErrMsg)
   661  				}
   662  				parseError := err.(*Error).Entries[0]
   663  				r := parseError.Context
   664  
   665  				if errPart := test.code[r.From:r.To]; errPart != test.wantErrPart {
   666  					t.Errorf("Parse(%q) returns error with part %q, want %q",
   667  						test.code, errPart, test.wantErrPart)
   668  				}
   669  				if atEnd := r.From == len(test.code); atEnd != test.wantErrAtEnd {
   670  					t.Errorf("Parse(%q) returns error at end = %v, want %v",
   671  						test.code, atEnd, test.wantErrAtEnd)
   672  				}
   673  				if errMsg := parseError.Message; errMsg != test.wantErrMsg {
   674  					t.Errorf("Parse(%q) returns error with message %q, want %q",
   675  						test.code, errMsg, test.wantErrMsg)
   676  				}
   677  			}
   678  		})
   679  	}
   680  }
   681  
   682  func TestParse_ReturnsTreeContainingSourceFromArgument(t *testing.T) {
   683  	src := SourceForTest("a")
   684  	tree, _ := Parse(src, Config{})
   685  	if tree.Source != src {
   686  		t.Errorf("tree.Source = %v, want %v", tree.Source, src)
   687  	}
   688  }