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 }