github.com/elves/Elvish@v0.12.0/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 goodCases = []struct {
    15  	src string
    16  	ast ast
    17  }{
    18  	// Chunk
    19  	// Smoke test.
    20  	{"a;b;c\n;d", ast{"Chunk", fs{"Pipelines": []string{"a", "b", "c", "d"}}}},
    21  	// Empty chunk should have Pipelines=nil.
    22  	{"", ast{"Chunk", fs{"Pipelines": nil}}},
    23  	// Superfluous newlines and semicolons should not result in empty
    24  	// pipelines.
    25  	{"  ;\n\n  ls \t ;\n", ast{"Chunk", fs{"Pipelines": []string{"ls \t "}}}},
    26  
    27  	// Pipeline
    28  	{"a|b|c|d", ast{
    29  		"Chunk/Pipeline", fs{"Forms": []string{"a", "b", "c", "d"}}}},
    30  	// Newlines are allowed after pipes.
    31  	{"a| \n \n b", ast{
    32  		"Chunk/Pipeline", fs{"Forms": []string{"a", "b"}}}},
    33  	// Comments.
    34  	{"a#haha\nb#lala", ast{
    35  		"Chunk", fs{"Pipelines": []string{"a", "b"}}}},
    36  
    37  	// Form
    38  	// Smoke test.
    39  	{"ls x y", ast{"Chunk/Pipeline/Form", fs{
    40  		"Head": "ls",
    41  		"Args": []string{"x", "y"}}}},
    42  	// Assignments.
    43  	{"k=v k[a][b]=v {a,b[1]}=(ha)", ast{"Chunk/Pipeline/Form", fs{
    44  		"Assignments": []string{"k=v", "k[a][b]=v", "{a,b[1]}=(ha)"}}}},
    45  	// Temporary assignment.
    46  	{"k=v k[a][b]=v a", ast{"Chunk/Pipeline/Form", fs{
    47  		"Assignments": []string{"k=v", "k[a][b]=v"},
    48  		"Head":        "a"}}},
    49  	// Spacey assignment.
    50  	{"k=v a b = c d", ast{"Chunk/Pipeline/Form", fs{
    51  		"Assignments": []string{"k=v"},
    52  		"Vars":        []string{"a", "b"},
    53  		"Args":        []string{"c", "d"}}}},
    54  	// Redirections
    55  	{"a >b", ast{"Chunk/Pipeline/Form", fs{
    56  		"Head": "a",
    57  		"Redirs": []ast{
    58  			{"Redir", fs{"Mode": Write, "Right": "b"}}},
    59  	}}},
    60  	// More redirections
    61  	{"a >>b 2>b 3>&- 4>&1 5<c 6<>d", ast{"Chunk/Pipeline/Form", fs{
    62  		"Head": "a",
    63  		"Redirs": []ast{
    64  			{"Redir", fs{"Mode": Append, "Right": "b"}},
    65  			{"Redir", fs{"Left": "2", "Mode": Write, "Right": "b"}},
    66  			{"Redir", fs{"Left": "3", "Mode": Write, "RightIsFd": true, "Right": "-"}},
    67  			{"Redir", fs{"Left": "4", "Mode": Write, "RightIsFd": true, "Right": "1"}},
    68  			{"Redir", fs{"Left": "5", "Mode": Read, "Right": "c"}},
    69  			{"Redir", fs{"Left": "6", "Mode": ReadWrite, "Right": "d"}},
    70  		},
    71  	}}},
    72  	// Exitus redirection
    73  	{"a ?>$e", ast{"Chunk/Pipeline/Form", fs{
    74  		"Head":        "a",
    75  		"ExitusRedir": ast{"ExitusRedir", fs{"Dest": "$e"}},
    76  	}}},
    77  	// Options (structure of MapPair tested below with map)
    78  	{"a &a=1 x &b=2", ast{"Chunk/Pipeline/Form", fs{
    79  		"Head": "a",
    80  		"Args": []string{"x"},
    81  		"Opts": []string{"&a=1", "&b=2"},
    82  	}}},
    83  
    84  	// Compound
    85  	{`a b"foo"?$c*'xyz'`, a(ast{"Compound", fs{
    86  		"Indexings": []string{"b", `"foo"`, "?", "$c", "*", "'xyz'"}}})},
    87  
    88  	// Indexing
    89  	{"a $b[c][d][\ne\n]", a(ast{"Compound/Indexing", fs{
    90  		"Head": "$b", "Indicies": []string{"c", "d", "\ne\n"},
    91  	}})},
    92  
    93  	// Primary
    94  	//
    95  	// Single quote
    96  	{"a '''x''y'''", a(ast{"Compound/Indexing/Primary", fs{
    97  		"Type": SingleQuoted, "Value": "'x'y'",
    98  	}})},
    99  	// Double quote
   100  	{`a "b\^[\x1b\u548c\U0002CE23\123\n\t\\"`,
   101  		a(ast{"Compound/Indexing/Primary", fs{
   102  			"Type":  DoubleQuoted,
   103  			"Value": "b\x1b\x1b\u548c\U0002CE23\123\n\t\\",
   104  		}})},
   105  	// Wildcard
   106  	{"a * ?", a(
   107  		ast{"Compound/Indexing/Primary", fs{"Type": Wildcard, "Value": "*"}},
   108  		ast{"Compound/Indexing/Primary", fs{"Type": Wildcard, "Value": "?"}},
   109  	)},
   110  	// Variable
   111  	{"a $x $&f", a(
   112  		ast{"Compound/Indexing/Primary", fs{"Type": Variable, "Value": "x"}},
   113  		ast{"Compound/Indexing/Primary", fs{"Type": Variable, "Value": "&f"}},
   114  	)},
   115  	// List
   116  	{"a [] [ ] [1] [ 2] [3 ] [\n 4 \n5\n 6 7 \n]", a(
   117  		ast{"Compound/Indexing/Primary", fs{
   118  			"Type":     List,
   119  			"Elements": []ast{}}},
   120  		ast{"Compound/Indexing/Primary", fs{
   121  			"Type":     List,
   122  			"Elements": []ast{}}},
   123  		ast{"Compound/Indexing/Primary", fs{
   124  			"Type":     List,
   125  			"Elements": []string{"1"}}},
   126  		ast{"Compound/Indexing/Primary", fs{
   127  			"Type":     List,
   128  			"Elements": []string{"2"}}},
   129  		ast{"Compound/Indexing/Primary", fs{
   130  			"Type":     List,
   131  			"Elements": []string{"3"}}},
   132  		ast{"Compound/Indexing/Primary", fs{
   133  			"Type":     List,
   134  			"Elements": []string{"4", "5", "6", "7"}}},
   135  	)},
   136  	// Map
   137  	{"a [&k=v] [ &k=v] [&k=v ] [ &k=v ] [ &k= v] [&k= \n v] [\n&a=b &c=d \n &e=f\n\n]", a(
   138  		ast{"Compound/Indexing/Primary", fs{
   139  			"Type":     Map,
   140  			"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
   141  		ast{"Compound/Indexing/Primary", fs{
   142  			"Type":     Map,
   143  			"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
   144  		ast{"Compound/Indexing/Primary", fs{
   145  			"Type":     Map,
   146  			"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
   147  		ast{"Compound/Indexing/Primary", fs{
   148  			"Type":     Map,
   149  			"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
   150  		ast{"Compound/Indexing/Primary", fs{
   151  			"Type":     Map,
   152  			"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
   153  		ast{"Compound/Indexing/Primary", fs{
   154  			"Type":     Map,
   155  			"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
   156  		ast{"Compound/Indexing/Primary", fs{
   157  			"Type": Map,
   158  			"MapPairs": []ast{
   159  				{"MapPair", fs{"Key": "a", "Value": "b"}},
   160  				{"MapPair", fs{"Key": "c", "Value": "d"}},
   161  				{"MapPair", fs{"Key": "e", "Value": "f"}},
   162  			}}},
   163  	)},
   164  	// Empty map
   165  	{"a [&] [ &] [& ] [ & ]", a(
   166  		ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
   167  		ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
   168  		ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
   169  		ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
   170  	)},
   171  	// Lambda
   172  	{"a []{} [ ]{ } []{ echo 233 } [ x y ]{puts $x $y} { put haha}", a(
   173  		ast{"Compound/Indexing/Primary", fs{
   174  			"Type": Lambda, "Elements": []ast{}, "Chunk": "",
   175  		}},
   176  		ast{"Compound/Indexing/Primary", fs{
   177  			"Type": Lambda, "Elements": []ast{}, "Chunk": " ",
   178  		}},
   179  		ast{"Compound/Indexing/Primary", fs{
   180  			"Type": Lambda, "Elements": []ast{}, "Chunk": " echo 233 ",
   181  		}},
   182  		ast{"Compound/Indexing/Primary", fs{
   183  			"Type": Lambda, "Elements": []string{"x", "y"}, "Chunk": "puts $x $y",
   184  		}},
   185  		ast{"Compound/Indexing/Primary", fs{
   186  			"Type": Lambda, "Elements": []ast{}, "Chunk": " put haha",
   187  		}},
   188  	)},
   189  	// Lambda with arguments and options
   190  	{"a [a b &k=v]{}", a(
   191  		ast{"Compound/Indexing/Primary", fs{
   192  			"Type":     Lambda,
   193  			"Elements": []string{"a", "b"},
   194  			"MapPairs": []string{"&k=v"},
   195  			"Chunk":    "",
   196  		}},
   197  	)},
   198  	// Output capture
   199  	{"a () (b;c) (c\nd)", a(
   200  		ast{"Compound/Indexing/Primary", fs{
   201  			"Type": OutputCapture, "Chunk": ""}},
   202  		ast{"Compound/Indexing/Primary", fs{
   203  			"Type": OutputCapture, "Chunk": ast{
   204  				"Chunk", fs{"Pipelines": []string{"b", "c"}},
   205  			}}},
   206  		ast{"Compound/Indexing/Primary", fs{
   207  			"Type": OutputCapture, "Chunk": ast{
   208  				"Chunk", fs{"Pipelines": []string{"c", "d"}},
   209  			}}},
   210  	)},
   211  	// Exitus capture
   212  	{"a ?() ?(b;c)", a(
   213  		ast{"Compound/Indexing/Primary", fs{
   214  			"Type": ExceptionCapture, "Chunk": ""}},
   215  		ast{"Compound/Indexing/Primary", fs{
   216  			"Type": ExceptionCapture, "Chunk": "b;c",
   217  		}})},
   218  	// Braced
   219  	{"a {,a,c\ng\n}", a(
   220  		ast{"Compound/Indexing/Primary", fs{
   221  			"Type":   Braced,
   222  			"Braced": []string{"", "a", "c", "g", ""}}})},
   223  	// Tilde
   224  	{"a ~xiaq/go", a(
   225  		ast{"Compound", fs{
   226  			"Indexings": []ast{
   227  				{"Indexing/Primary", fs{"Type": Tilde, "Value": "~"}},
   228  				{"Indexing/Primary", fs{"Type": Bareword, "Value": "xiaq/go"}},
   229  			},
   230  		}},
   231  	)},
   232  
   233  	// Line continuation: "\\\n" is considered whitespace
   234  	{"a b\\\nc", ast{
   235  		"Chunk/Pipeline/Form", fs{"Head": "a", "Args": []string{"b", "c"}}}},
   236  }
   237  
   238  func TestParse(t *testing.T) {
   239  	for _, tc := range goodCases {
   240  		bn, err := Parse("[test]", tc.src)
   241  		if err != nil {
   242  			t.Errorf("Parse(%q) returns error: %v", tc.src, err)
   243  		}
   244  		err = checkParseTree(bn)
   245  		if err != nil {
   246  			t.Errorf("Parse(%q) returns bad parse tree: %v", tc.src, err)
   247  			fmt.Fprintf(os.Stderr, "Parse tree of %q:\n", tc.src)
   248  			PPrintParseTreeTo(bn, os.Stderr)
   249  		}
   250  		err = checkAST(bn, tc.ast)
   251  		if err != nil {
   252  			t.Errorf("Parse(%q) returns bad AST: %v", tc.src, err)
   253  			fmt.Fprintf(os.Stderr, "AST of %q:\n", tc.src)
   254  			PPrintASTTo(bn, os.Stderr)
   255  		}
   256  	}
   257  }
   258  
   259  var badCases = []struct {
   260  	src string
   261  	pos int // expected Begin position of first error
   262  }{
   263  	// Empty form.
   264  	{"a|", 2},
   265  	// Unopened parens.
   266  	{")", 0}, {"]", 0}, {"}", 0},
   267  	// Unclosed parens.
   268  	{"a (", 3}, {"a [", 3}, {"a {", 3},
   269  	// Bogus ampersand.
   270  	{"a & &", 4}, {"a [&", 4},
   271  }
   272  
   273  func TestParseError(t *testing.T) {
   274  	for _, tc := range badCases {
   275  		_, err := Parse("[test]", tc.src)
   276  		if err == nil {
   277  			t.Errorf("Parse(%q) returns no error", tc.src)
   278  			continue
   279  		}
   280  		posErr0 := err.(*Error).Entries[0]
   281  		if posErr0.Context.Begin != tc.pos {
   282  			t.Errorf("Parse(%q) first error begins at %d, want %d. Errors are:%s\n", tc.src, posErr0.Context.Begin, tc.pos, err)
   283  		}
   284  	}
   285  }