github.com/elves/elvish@v0.15.0/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 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 34 // Form 35 // Smoke test. 36 {"ls x y", ast{"Chunk/Pipeline/Form", fs{ 37 "Head": "ls", 38 "Args": []string{"x", "y"}}}}, 39 // Assignments. 40 {"k=v k[a][b]=v {a,b[1]}=(ha)", ast{"Chunk/Pipeline/Form", fs{ 41 "Assignments": []string{"k=v", "k[a][b]=v", "{a,b[1]}=(ha)"}}}}, 42 // Temporary assignment. 43 {"k=v k[a][b]=v a", ast{"Chunk/Pipeline/Form", fs{ 44 "Assignments": []string{"k=v", "k[a][b]=v"}, 45 "Head": "a"}}}, 46 // Redirections 47 {"a >b", ast{"Chunk/Pipeline/Form", fs{ 48 "Head": "a", 49 "Redirs": []ast{ 50 {"Redir", fs{"Mode": Write, "Right": "b"}}}, 51 }}}, 52 // More redirections 53 {"a >>b 2>b 3>&- 4>&1 5<c 6<>d", ast{"Chunk/Pipeline/Form", fs{ 54 "Head": "a", 55 "Redirs": []ast{ 56 {"Redir", fs{"Mode": Append, "Right": "b"}}, 57 {"Redir", fs{"Left": "2", "Mode": Write, "Right": "b"}}, 58 {"Redir", fs{"Left": "3", "Mode": Write, "RightIsFd": true, "Right": "-"}}, 59 {"Redir", fs{"Left": "4", "Mode": Write, "RightIsFd": true, "Right": "1"}}, 60 {"Redir", fs{"Left": "5", "Mode": Read, "Right": "c"}}, 61 {"Redir", fs{"Left": "6", "Mode": ReadWrite, "Right": "d"}}, 62 }, 63 }}}, 64 // Options (structure of MapPair tested below with map) 65 {"a &a=1 x &b=2", ast{"Chunk/Pipeline/Form", fs{ 66 "Head": "a", 67 "Args": []string{"x"}, 68 "Opts": []string{"&a=1", "&b=2"}, 69 }}}, 70 71 // Compound 72 {`a b"foo"?$c*'xyz'`, a(ast{"Compound", fs{ 73 "Indexings": []string{"b", `"foo"`, "?", "$c", "*", "'xyz'"}}})}, 74 75 // Indexing 76 {"a $b[c][d][\ne\n]", a(ast{"Compound/Indexing", fs{ 77 "Head": "$b", "Indicies": []string{"c", "d", "\ne\n"}, 78 }})}, 79 80 // Primary 81 82 // Bareword. 83 {"a foo", a(ast{"Compound/Indexing/Primary", fs{ 84 "Type": Bareword, "Value": "foo", 85 }})}, 86 87 // Bareword, with all allowed symbols. 88 {"a ./\\@%+!=,", a(ast{"Compound/Indexing/Primary", fs{ 89 "Type": Bareword, "Value": "./\\@%+!=,", 90 }})}, 91 92 // Single quote 93 {"a '''x''y'''", a(ast{"Compound/Indexing/Primary", fs{ 94 "Type": SingleQuoted, "Value": "'x'y'", 95 }})}, 96 // Double quote 97 {`a "[\c?\c@\cI\^I\^[]"`, // control char sequences 98 a(ast{"Compound/Indexing/Primary", fs{ 99 "Type": DoubleQuoted, 100 "Value": "[\x7f\x00\t\t\x1b]", 101 }})}, 102 103 {`a "[\n\t\a\v\\\"]"`, // single char sequences 104 a(ast{"Compound/Indexing/Primary", fs{ 105 "Type": DoubleQuoted, 106 "Value": "[\n\t\a\v\\\"]", 107 }})}, 108 109 {`a "b\^[\x1b\u548c\U0002CE23\123\n\t\\"`, // numeric sequences 110 a(ast{"Compound/Indexing/Primary", fs{ 111 "Type": DoubleQuoted, 112 "Value": "b\x1b\x1b\u548c\U0002CE23\123\n\t\\", 113 }})}, 114 // Wildcard 115 {"a * ? ** ??", a( 116 ast{"Compound/Indexing/Primary", fs{"Type": Wildcard, "Value": "*"}}, 117 ast{"Compound/Indexing/Primary", fs{"Type": Wildcard, "Value": "?"}}, 118 ast{"Compound/Indexing/Primary", fs{"Type": Wildcard, "Value": "**"}}, 119 ast{"Compound", fs{"Indexings": []string{"?", "?"}}}, 120 )}, 121 // Variable 122 {`a $x $'!@#' $"\n"`, a( 123 ast{"Compound/Indexing/Primary", fs{"Type": Variable, "Value": "x"}}, 124 ast{"Compound/Indexing/Primary", fs{"Type": Variable, "Value": "!@#"}}, 125 ast{"Compound/Indexing/Primary", fs{"Type": Variable, "Value": "\n"}}, 126 )}, 127 // List 128 {"a [] [ ] [1] [ 2] [3 ] [\n 4 \n5\n 6 7 \n]", a( 129 ast{"Compound/Indexing/Primary", fs{ 130 "Type": List, 131 "Elements": []ast{}}}, 132 ast{"Compound/Indexing/Primary", fs{ 133 "Type": List, 134 "Elements": []ast{}}}, 135 ast{"Compound/Indexing/Primary", fs{ 136 "Type": List, 137 "Elements": []string{"1"}}}, 138 ast{"Compound/Indexing/Primary", fs{ 139 "Type": List, 140 "Elements": []string{"2"}}}, 141 ast{"Compound/Indexing/Primary", fs{ 142 "Type": List, 143 "Elements": []string{"3"}}}, 144 ast{"Compound/Indexing/Primary", fs{ 145 "Type": List, 146 "Elements": []string{"4", "5", "6", "7"}}}, 147 )}, 148 // Map 149 {"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( 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{{"MapPair", fs{"Key": "k", "Value": "v"}}}}}, 168 ast{"Compound/Indexing/Primary", fs{ 169 "Type": Map, 170 "MapPairs": []ast{ 171 {"MapPair", fs{"Key": "a", "Value": "b"}}, 172 {"MapPair", fs{"Key": "c", "Value": "d"}}, 173 {"MapPair", fs{"Key": "e", "Value": "f"}}, 174 }}}, 175 )}, 176 // Empty map 177 {"a [&] [ &] [& ] [ & ]", a( 178 ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}}, 179 ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}}, 180 ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}}, 181 ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}}, 182 )}, 183 // Lambda 184 {"a []{} [ ]{ } []{ echo 233 } [ x y ]{puts $x $y} { put haha}", a( 185 ast{"Compound/Indexing/Primary", fs{ 186 "Type": Lambda, "Elements": []ast{}, "Chunk": "", 187 }}, 188 ast{"Compound/Indexing/Primary", fs{ 189 "Type": Lambda, "Elements": []ast{}, "Chunk": " ", 190 }}, 191 ast{"Compound/Indexing/Primary", fs{ 192 "Type": Lambda, "Elements": []ast{}, "Chunk": " echo 233 ", 193 }}, 194 ast{"Compound/Indexing/Primary", fs{ 195 "Type": Lambda, "Elements": []string{"x", "y"}, "Chunk": "puts $x $y", 196 }}, 197 ast{"Compound/Indexing/Primary", fs{ 198 "Type": Lambda, "Elements": []ast{}, "Chunk": " put haha", 199 }}, 200 )}, 201 // Lambda with arguments and options 202 {"a [a b &k=v]{}", a( 203 ast{"Compound/Indexing/Primary", fs{ 204 "Type": Lambda, 205 "Elements": []string{"a", "b"}, 206 "MapPairs": []string{"&k=v"}, 207 "Chunk": "", 208 }}, 209 )}, 210 // Output capture 211 {"a () (b;c) (c\nd)", a( 212 ast{"Compound/Indexing/Primary", fs{ 213 "Type": OutputCapture, "Chunk": ""}}, 214 ast{"Compound/Indexing/Primary", fs{ 215 "Type": OutputCapture, "Chunk": ast{ 216 "Chunk", fs{"Pipelines": []string{"b", "c"}}, 217 }}}, 218 ast{"Compound/Indexing/Primary", fs{ 219 "Type": OutputCapture, "Chunk": ast{ 220 "Chunk", fs{"Pipelines": []string{"c", "d"}}, 221 }}}, 222 )}, 223 // Exitus capture 224 {"a ?() ?(b;c)", a( 225 ast{"Compound/Indexing/Primary", fs{ 226 "Type": ExceptionCapture, "Chunk": ""}}, 227 ast{"Compound/Indexing/Primary", fs{ 228 "Type": ExceptionCapture, "Chunk": "b;c", 229 }})}, 230 // Braced 231 {"a {,a,c\ng\n}", a( 232 ast{"Compound/Indexing/Primary", fs{ 233 "Type": Braced, 234 "Braced": []string{"", "a", "c", "g", ""}}})}, 235 // Tilde 236 {"a ~xiaq/go", a( 237 ast{"Compound", fs{ 238 "Indexings": []ast{ 239 {"Indexing/Primary", fs{"Type": Tilde, "Value": "~"}}, 240 {"Indexing/Primary", fs{"Type": Bareword, "Value": "xiaq/go"}}, 241 }, 242 }}, 243 )}, 244 // Tilde and wildcard 245 {"a ~xiaq/*.go", a( 246 ast{"Compound", fs{ 247 "Indexings": []ast{ 248 {"Indexing/Primary", fs{"Type": Tilde, "Value": "~"}}, 249 {"Indexing/Primary", fs{"Type": Bareword, "Value": "xiaq/"}}, 250 {"Indexing/Primary", fs{"Type": Wildcard, "Value": "*"}}, 251 {"Indexing/Primary", fs{"Type": Bareword, "Value": ".go"}}, 252 }, 253 }}, 254 )}, 255 256 // Line continuation: "^\n" is considered whitespace 257 {"a b^\nc", ast{ 258 "Chunk/Pipeline/Form", fs{"Head": "a", "Args": []string{"b", "c"}}}}, 259 260 // Carriage returns are normally treated the same as newlines: 261 // Separating pipelines in a chunk 262 {"a\rb", ast{"Chunk", fs{"Pipelines": []string{"a", "b"}}}}, 263 {"a\r\nb", ast{"Chunk", fs{"Pipelines": []string{"a", "b"}}}}, 264 // Whitespace padding in lambdas 265 {"a { \rfoo\r\nbar }", a( 266 ast{"Compound/Indexing/Primary", 267 fs{"Type": Lambda, "Chunk": " \rfoo\r\nbar "}}, 268 )}, 269 // Separating elements in lists 270 {"a [a\rb]", a( 271 ast{"Compound/Indexing/Primary", fs{ 272 "Type": List, 273 "Elements": []string{"a", "b"}}})}, 274 275 // However, in line continuations, \r\n is treated as a single newline 276 {"a b^\r\nc", ast{ 277 "Chunk/Pipeline/Form", fs{"Head": "a", "Args": []string{"b", "c"}}}}, 278 // But a lone \r also works 279 {"a b^\rc", ast{ 280 "Chunk/Pipeline/Form", fs{"Head": "a", "Args": []string{"b", "c"}}}}, 281 282 // Comments in chunks. 283 {"a#haha\nb#lala", ast{ 284 "Chunk", fs{"Pipelines": []ast{ 285 {"Pipeline/Form", fs{"Head": "a"}}, 286 {"Pipeline/Form", fs{"Head": "b"}}, 287 }}}}, 288 // Comments in lists. 289 {"a [a#haha\nb]", a( 290 ast{"Compound/Indexing/Primary", fs{ 291 "Type": List, 292 "Elements": []string{"a", "b"}, 293 }}, 294 )}, 295 } 296 297 func TestParse(t *testing.T) { 298 for _, tc := range goodCases { 299 src := SourceForTest(tc.src) 300 tree, err := Parse(src) 301 if err != nil { 302 t.Errorf("Parse(%q) returns error: %v", tc.src, err) 303 } 304 if tree.Source != src { 305 t.Errorf("Parse(%q) returns source %v, want %v", tc.src, tree.Source, src) 306 } 307 err = checkParseTree(tree.Root) 308 if err != nil { 309 t.Errorf("Parse(%q) returns bad parse tree: %v", tc.src, err) 310 fmt.Fprintf(os.Stderr, "Parse tree of %q:\n", tc.src) 311 pprintParseTree(tree.Root, os.Stderr) 312 } 313 err = checkAST(tree.Root, tc.ast) 314 if err != nil { 315 t.Errorf("Parse(%q) returns bad AST: %v", tc.src, err) 316 fmt.Fprintf(os.Stderr, "AST of %q:\n", tc.src) 317 pprintAST(tree.Root, os.Stderr) 318 } 319 } 320 } 321 322 var parseErrorTests = []struct { 323 src string 324 errPart string 325 errAtEnd bool 326 errMsg string 327 }{ 328 // Empty form. 329 {src: "a|", errAtEnd: true, errMsg: "should be form"}, 330 // Unopened parens. 331 {src: ")", errPart: ")", errMsg: "unexpected rune ')'"}, 332 {src: "]", errPart: "]", errMsg: "unexpected rune ']'"}, 333 {src: "}", errPart: "}", errMsg: "unexpected rune '}'"}, 334 // Unclosed parens. 335 {src: "a (", errAtEnd: true, errMsg: "should be ')'"}, 336 {src: "a [", errAtEnd: true, errMsg: "should be ']'"}, 337 {src: "a {", errAtEnd: true, errMsg: "should be ',' or '}'"}, 338 // Bogus ampersand in form. 339 {src: "a & &", errPart: "&", errMsg: "unexpected rune '&'"}, 340 // No redirection source. 341 {src: "a >", errAtEnd: true, errMsg: "should be a composite term representing filename"}, 342 {src: "a >&", errAtEnd: true, errMsg: "should be a composite term representing fd"}, 343 // Unmatched paren in indexing. 344 {src: "a $a[0}", errPart: "}", errMsg: "should be ']'"}, 345 // Unterminated string. 346 {src: "'a", errAtEnd: true, errMsg: "string not terminated"}, 347 {src: `"a`, errAtEnd: true, errMsg: "string not terminated"}, 348 // Bad escape sequence. 349 {src: `a "\^` + "\t", errPart: "\t", 350 errMsg: "invalid control sequence, should be a codepoint between 0x3F and 0x5F"}, 351 {src: `a "\xQQ"`, errPart: "Q", errMsg: "invalid escape sequence, should be hex digit"}, 352 {src: `a "\1ab"`, errPart: "a", errMsg: "invalid escape sequence, should be octal digit"}, 353 {src: `a "\i"`, errPart: "i", errMsg: "invalid escape sequence"}, 354 // Unterminated variable name. 355 {src: "$", errAtEnd: true, errMsg: "should be variable name"}, 356 // Unmatched (. 357 {src: "a (", errAtEnd: true, errMsg: "should be ')'"}, 358 // List-map hybrid. 359 // TODO(xiaq): Add correct position information. 360 {src: "a [a &k=v]", errAtEnd: true, errMsg: "cannot contain both list elements and map pairs"}, 361 // Unmatched {. 362 {src: "{ a", errAtEnd: true, errMsg: "should be '}'"}, 363 // Unfinished line continuation. 364 {src: `a ^`, errAtEnd: true, errMsg: "should be newline"}, 365 } 366 367 func TestParseError(t *testing.T) { 368 for _, test := range parseErrorTests { 369 t.Run(test.src, func(t *testing.T) { 370 _, err := Parse(SourceForTest(test.src)) 371 if err == nil { 372 t.Fatalf("no error") 373 } 374 parseError := err.(*Error).Entries[0] 375 r := parseError.Context 376 377 if errPart := test.src[r.From:r.To]; errPart != test.errPart { 378 t.Errorf("err part is %q, want %q", errPart, test.errPart) 379 } 380 if errAtEnd := r.From == len(test.src); errAtEnd != test.errAtEnd { 381 t.Errorf("err at end is %v, want %v", errAtEnd, test.errAtEnd) 382 } 383 if errMsg := parseError.Message; errMsg != test.errMsg { 384 t.Errorf("err message is %q, want %q", errMsg, test.errMsg) 385 } 386 }) 387 } 388 }