github.com/ladydascalie/elvish@v0.0.0-20170703214355-2964dd3ece7f/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  			"List": ""}},
   120  		ast{"Compound/Indexing/Primary", fs{
   121  			"Type": List,
   122  			"List": ""}},
   123  		ast{"Compound/Indexing/Primary", fs{
   124  			"Type": List,
   125  			"List": ast{"Array", fs{"Compounds": []string{"1"}}}}},
   126  		ast{"Compound/Indexing/Primary", fs{
   127  			"Type": List,
   128  			"List": ast{"Array", fs{"Compounds": []string{"2"}}}}},
   129  		ast{"Compound/Indexing/Primary", fs{
   130  			"Type": List,
   131  			"List": ast{"Array", fs{"Compounds": []string{"3"}}}}},
   132  		ast{"Compound/Indexing/Primary", fs{
   133  			"Type": List,
   134  			"List": ast{"Array", fs{
   135  				"Compounds": []string{"4", "5", "6", "7"}}}}},
   136  	)},
   137  	// Semicolons in lists
   138  	{"a [a b;c;d;]", a(
   139  		ast{"Compound/Indexing/Primary", fs{
   140  			"Type": List,
   141  			"List": ast{"Array", fs{
   142  				"Compounds":  []string{"a", "b", "c", "d"},
   143  				"Semicolons": []int{2, 3, 4}}}}},
   144  	)},
   145  	// Map
   146  	{"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(
   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{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
   159  		ast{"Compound/Indexing/Primary", fs{
   160  			"Type":     Map,
   161  			"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
   162  		ast{"Compound/Indexing/Primary", fs{
   163  			"Type":     Map,
   164  			"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
   165  		ast{"Compound/Indexing/Primary", fs{
   166  			"Type": Map,
   167  			"MapPairs": []ast{
   168  				{"MapPair", fs{"Key": "a", "Value": "b"}},
   169  				{"MapPair", fs{"Key": "c", "Value": "d"}},
   170  				{"MapPair", fs{"Key": "e", "Value": "f"}},
   171  			}}},
   172  	)},
   173  	// Empty map
   174  	{"a [&] [ &] [& ] [ & ]", a(
   175  		ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
   176  		ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
   177  		ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
   178  		ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
   179  	)},
   180  	// Lambda
   181  	{"a []{} [ ]{ } []{ echo 233 } [ $x $y ]{puts $x $y} { put $1}", a(
   182  		ast{"Compound/Indexing/Primary", fs{
   183  			"Type": Lambda, "List": "", "Chunk": "",
   184  		}},
   185  		ast{"Compound/Indexing/Primary", fs{
   186  			"Type": Lambda, "List": "", "Chunk": " ",
   187  		}},
   188  		ast{"Compound/Indexing/Primary", fs{
   189  			"Type": Lambda, "List": "", "Chunk": " echo 233 ",
   190  		}},
   191  		ast{"Compound/Indexing/Primary", fs{
   192  			"Type": Lambda, "List": "$x $y ", "Chunk": "puts $x $y",
   193  		}},
   194  		ast{"Compound/Indexing/Primary", fs{
   195  			"Type": Lambda, "List": nil, "Chunk": " put $1",
   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  	// Output capture with backquotes
   212  	{"a `` `b;c` `e>f`", a("``", "`b;c`", "`e>f`")},
   213  	// Backquotes may be nested with unclosed parens and braces
   214  	{"a `a (b `c`)` `d [`e`]`", a("`a (b `c`)`", "`d [`e`]`")},
   215  	// Exitus capture
   216  	{"a ?() ?(b;c)", a(
   217  		ast{"Compound/Indexing/Primary", fs{
   218  			"Type": ExceptionCapture, "Chunk": ""}},
   219  		ast{"Compound/Indexing/Primary", fs{
   220  			"Type": ExceptionCapture, "Chunk": "b;c",
   221  		}})},
   222  	// Braced
   223  	{"a {,a,c\ng\n}", a(
   224  		ast{"Compound/Indexing/Primary", fs{
   225  			"Type":   Braced,
   226  			"Braced": []string{"", "a", "c", "g", ""}}})},
   227  	// Tilde
   228  	{"a ~xiaq/go", a(
   229  		ast{"Compound", fs{
   230  			"Indexings": []ast{
   231  				{"Indexing/Primary", fs{"Type": Tilde, "Value": "~"}},
   232  				{"Indexing/Primary", fs{"Type": Bareword, "Value": "xiaq/go"}},
   233  			},
   234  		}},
   235  	)},
   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  			PprintParseTree(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  			PprintAST(bn, os.Stderr)
   255  		}
   256  	}
   257  }
   258  
   259  // checkParseTree checks whether the parse tree part of a Node is well-formed.
   260  func checkParseTree(n Node) error {
   261  	children := n.Children()
   262  	if len(children) == 0 {
   263  		return nil
   264  	}
   265  
   266  	// Parent pointers of all children should point to me.
   267  	for i, ch := range children {
   268  		if ch.Parent() != n {
   269  			return fmt.Errorf("parent of child %d (%s) is wrong: %s", i, summary(ch), summary(n))
   270  		}
   271  	}
   272  
   273  	// The Begin of the first child should be equal to mine.
   274  	if children[0].Begin() != n.Begin() {
   275  		return fmt.Errorf("gap between node and first child: %s", summary(n))
   276  	}
   277  	// The End of the last child should be equal to mine.
   278  	nch := len(children)
   279  	if children[nch-1].End() != n.End() {
   280  		return fmt.Errorf("gap between node and last child: %s", summary(n))
   281  	}
   282  	// Consecutive children have consecutive position ranges.
   283  	for i := 0; i < nch-1; i++ {
   284  		if children[i].End() != children[i+1].Begin() {
   285  			return fmt.Errorf("gap between child %d and %d of: %s", i, i+1, summary(n))
   286  		}
   287  	}
   288  
   289  	// Check children recursively.
   290  	for _, ch := range n.Children() {
   291  		err := checkParseTree(ch)
   292  		if err != nil {
   293  			return err
   294  		}
   295  	}
   296  	return nil
   297  }
   298  
   299  var badCases = []struct {
   300  	src string
   301  	pos int // expected Begin position of first error
   302  }{
   303  	// Empty form.
   304  	{"a|", 2},
   305  	// Unopened parens.
   306  	{")", 0}, {"]", 0}, {"}", 0},
   307  	// Unclosed parens.
   308  	{"a (", 3}, {"a [", 3}, {"a {", 3},
   309  	// Bogus ampersand.
   310  	{"a & &", 4}, {"a [&", 4},
   311  }
   312  
   313  func TestParseError(t *testing.T) {
   314  	for _, tc := range badCases {
   315  		_, err := Parse("[test]", tc.src)
   316  		if err == nil {
   317  			t.Errorf("Parse(%q) returns no error", tc.src)
   318  			continue
   319  		}
   320  		posErr0 := err.(*Error).Entries[0]
   321  		if posErr0.Context.Begin != tc.pos {
   322  			t.Errorf("Parse(%q) first error begins at %d, want %d. Errors are:%s\n", tc.src, posErr0.Context.Begin, tc.pos, err)
   323  		}
   324  	}
   325  }