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 }