mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-13 09:57:51 +08:00
65508e728f
The Error type now implements the error interface.
286 lines
8.5 KiB
Go
286 lines
8.5 KiB
Go
package parse
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"testing"
|
|
)
|
|
|
|
func a(c ...interface{}) ast {
|
|
// Shorthand used for checking Compound and levels beneath.
|
|
return ast{"Chunk/Pipeline/Form", fs{"Head": "a", "Args": c}}
|
|
}
|
|
|
|
var goodCases = []struct {
|
|
src string
|
|
ast ast
|
|
}{
|
|
// Chunk
|
|
// Smoke test.
|
|
{"a;b;c\n;d", ast{"Chunk", fs{"Pipelines": []string{"a", "b", "c", "d"}}}},
|
|
// Empty chunk should have Pipelines=nil.
|
|
{"", ast{"Chunk", fs{"Pipelines": nil}}},
|
|
// Superfluous newlines and semicolons should not result in empty
|
|
// pipelines.
|
|
{" ;\n\n ls \t ;\n", ast{"Chunk", fs{"Pipelines": []string{"ls \t "}}}},
|
|
|
|
// Pipeline
|
|
{"a|b|c|d", ast{
|
|
"Chunk/Pipeline", fs{"Forms": []string{"a", "b", "c", "d"}}}},
|
|
// Newlines are allowed after pipes.
|
|
{"a| \n \n b", ast{
|
|
"Chunk/Pipeline", fs{"Forms": []string{"a", "b"}}}},
|
|
// Comments.
|
|
{"a#haha\nb#lala", ast{
|
|
"Chunk", fs{"Pipelines": []string{"a", "b"}}}},
|
|
|
|
// Form
|
|
// Smoke test.
|
|
{"ls x y", ast{"Chunk/Pipeline/Form", fs{
|
|
"Head": "ls",
|
|
"Args": []string{"x", "y"}}}},
|
|
// Assignments.
|
|
{"k=v k[a][b]=v {a,b[1]}=(ha)", ast{"Chunk/Pipeline/Form", fs{
|
|
"Assignments": []string{"k=v", "k[a][b]=v", "{a,b[1]}=(ha)"}}}},
|
|
// Temporary assignment.
|
|
{"k=v k[a][b]=v a", ast{"Chunk/Pipeline/Form", fs{
|
|
"Assignments": []string{"k=v", "k[a][b]=v"},
|
|
"Head": "a"}}},
|
|
// Spacey assignment.
|
|
{"k=v a b = c d", ast{"Chunk/Pipeline/Form", fs{
|
|
"Assignments": []string{"k=v"},
|
|
"Vars": []string{"a", "b"},
|
|
"Args": []string{"c", "d"}}}},
|
|
// Redirections
|
|
{"a >b", ast{"Chunk/Pipeline/Form", fs{
|
|
"Head": "a",
|
|
"Redirs": []ast{
|
|
{"Redir", fs{"Mode": Write, "Right": "b"}}},
|
|
}}},
|
|
// More redirections
|
|
{"a >>b 2>b 3>&- 4>&1 5<c 6<>d", ast{"Chunk/Pipeline/Form", fs{
|
|
"Head": "a",
|
|
"Redirs": []ast{
|
|
{"Redir", fs{"Mode": Append, "Right": "b"}},
|
|
{"Redir", fs{"Left": "2", "Mode": Write, "Right": "b"}},
|
|
{"Redir", fs{"Left": "3", "Mode": Write, "RightIsFd": true, "Right": "-"}},
|
|
{"Redir", fs{"Left": "4", "Mode": Write, "RightIsFd": true, "Right": "1"}},
|
|
{"Redir", fs{"Left": "5", "Mode": Read, "Right": "c"}},
|
|
{"Redir", fs{"Left": "6", "Mode": ReadWrite, "Right": "d"}},
|
|
},
|
|
}}},
|
|
// Exitus redirection
|
|
{"a ?>$e", ast{"Chunk/Pipeline/Form", fs{
|
|
"Head": "a",
|
|
"ExitusRedir": ast{"ExitusRedir", fs{"Dest": "$e"}},
|
|
}}},
|
|
// Options (structure of MapPair tested below with map)
|
|
{"a &a=1 x &b=2", ast{"Chunk/Pipeline/Form", fs{
|
|
"Head": "a",
|
|
"Args": []string{"x"},
|
|
"Opts": []string{"&a=1", "&b=2"},
|
|
}}},
|
|
|
|
// Compound
|
|
{`a b"foo"?$c*'xyz'`, a(ast{"Compound", fs{
|
|
"Indexings": []string{"b", `"foo"`, "?", "$c", "*", "'xyz'"}}})},
|
|
|
|
// Indexing
|
|
{"a $b[c][d][\ne\n]", a(ast{"Compound/Indexing", fs{
|
|
"Head": "$b", "Indicies": []string{"c", "d", "\ne\n"},
|
|
}})},
|
|
|
|
// Primary
|
|
//
|
|
// Single quote
|
|
{"a '''x''y'''", a(ast{"Compound/Indexing/Primary", fs{
|
|
"Type": SingleQuoted, "Value": "'x'y'",
|
|
}})},
|
|
// Double quote
|
|
{`a "b\^[\x1b\u548c\U0002CE23\123\n\t\\"`,
|
|
a(ast{"Compound/Indexing/Primary", fs{
|
|
"Type": DoubleQuoted,
|
|
"Value": "b\x1b\x1b\u548c\U0002CE23\123\n\t\\",
|
|
}})},
|
|
// Wildcard
|
|
{"a * ?", a(
|
|
ast{"Compound/Indexing/Primary", fs{"Type": Wildcard, "Value": "*"}},
|
|
ast{"Compound/Indexing/Primary", fs{"Type": Wildcard, "Value": "?"}},
|
|
)},
|
|
// Variable
|
|
{"a $x $&f", a(
|
|
ast{"Compound/Indexing/Primary", fs{"Type": Variable, "Value": "x"}},
|
|
ast{"Compound/Indexing/Primary", fs{"Type": Variable, "Value": "&f"}},
|
|
)},
|
|
// List
|
|
{"a [] [ ] [1] [ 2] [3 ] [\n 4 \n5\n 6 7 \n]", a(
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": List,
|
|
"Elements": []ast{}}},
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": List,
|
|
"Elements": []ast{}}},
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": List,
|
|
"Elements": []string{"1"}}},
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": List,
|
|
"Elements": []string{"2"}}},
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": List,
|
|
"Elements": []string{"3"}}},
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": List,
|
|
"Elements": []string{"4", "5", "6", "7"}}},
|
|
)},
|
|
// Map
|
|
{"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(
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": Map,
|
|
"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": Map,
|
|
"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": Map,
|
|
"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": Map,
|
|
"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": Map,
|
|
"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": Map,
|
|
"MapPairs": []ast{{"MapPair", fs{"Key": "k", "Value": "v"}}}}},
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": Map,
|
|
"MapPairs": []ast{
|
|
{"MapPair", fs{"Key": "a", "Value": "b"}},
|
|
{"MapPair", fs{"Key": "c", "Value": "d"}},
|
|
{"MapPair", fs{"Key": "e", "Value": "f"}},
|
|
}}},
|
|
)},
|
|
// Empty map
|
|
{"a [&] [ &] [& ] [ & ]", a(
|
|
ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
|
|
ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
|
|
ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
|
|
ast{"Compound/Indexing/Primary", fs{"Type": Map, "MapPairs": nil}},
|
|
)},
|
|
// Lambda
|
|
{"a []{} [ ]{ } []{ echo 233 } [ x y ]{puts $x $y} { put haha}", a(
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": Lambda, "Elements": []ast{}, "Chunk": "",
|
|
}},
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": Lambda, "Elements": []ast{}, "Chunk": " ",
|
|
}},
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": Lambda, "Elements": []ast{}, "Chunk": " echo 233 ",
|
|
}},
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": Lambda, "Elements": []string{"x", "y"}, "Chunk": "puts $x $y",
|
|
}},
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": Lambda, "Elements": []ast{}, "Chunk": " put haha",
|
|
}},
|
|
)},
|
|
// Lambda with arguments and options
|
|
{"a [a b &k=v]{}", a(
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": Lambda,
|
|
"Elements": []string{"a", "b"},
|
|
"MapPairs": []string{"&k=v"},
|
|
"Chunk": "",
|
|
}},
|
|
)},
|
|
// Output capture
|
|
{"a () (b;c) (c\nd)", a(
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": OutputCapture, "Chunk": ""}},
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": OutputCapture, "Chunk": ast{
|
|
"Chunk", fs{"Pipelines": []string{"b", "c"}},
|
|
}}},
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": OutputCapture, "Chunk": ast{
|
|
"Chunk", fs{"Pipelines": []string{"c", "d"}},
|
|
}}},
|
|
)},
|
|
// Exitus capture
|
|
{"a ?() ?(b;c)", a(
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": ExceptionCapture, "Chunk": ""}},
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": ExceptionCapture, "Chunk": "b;c",
|
|
}})},
|
|
// Braced
|
|
{"a {,a,c\ng\n}", a(
|
|
ast{"Compound/Indexing/Primary", fs{
|
|
"Type": Braced,
|
|
"Braced": []string{"", "a", "c", "g", ""}}})},
|
|
// Tilde
|
|
{"a ~xiaq/go", a(
|
|
ast{"Compound", fs{
|
|
"Indexings": []ast{
|
|
{"Indexing/Primary", fs{"Type": Tilde, "Value": "~"}},
|
|
{"Indexing/Primary", fs{"Type": Bareword, "Value": "xiaq/go"}},
|
|
},
|
|
}},
|
|
)},
|
|
|
|
// Line continuation: "\\\n" is considered whitespace
|
|
{"a b\\\nc", ast{
|
|
"Chunk/Pipeline/Form", fs{"Head": "a", "Args": []string{"b", "c"}}}},
|
|
}
|
|
|
|
func TestParse(t *testing.T) {
|
|
for _, tc := range goodCases {
|
|
bn, err := AsChunk("[test]", tc.src)
|
|
if err != nil {
|
|
t.Errorf("Parse(%q) returns error: %v", tc.src, err)
|
|
}
|
|
err = checkParseTree(bn)
|
|
if err != nil {
|
|
t.Errorf("Parse(%q) returns bad parse tree: %v", tc.src, err)
|
|
fmt.Fprintf(os.Stderr, "Parse tree of %q:\n", tc.src)
|
|
PPrintParseTree(bn, os.Stderr)
|
|
}
|
|
err = checkAST(bn, tc.ast)
|
|
if err != nil {
|
|
t.Errorf("Parse(%q) returns bad AST: %v", tc.src, err)
|
|
fmt.Fprintf(os.Stderr, "AST of %q:\n", tc.src)
|
|
PPrintAST(bn, os.Stderr)
|
|
}
|
|
}
|
|
}
|
|
|
|
var badCases = []struct {
|
|
src string
|
|
pos int // expected Begin position of first error
|
|
}{
|
|
// Empty form.
|
|
{"a|", 2},
|
|
// Unopened parens.
|
|
{")", 0}, {"]", 0}, {"}", 0},
|
|
// Unclosed parens.
|
|
{"a (", 3}, {"a [", 3}, {"a {", 3},
|
|
// Bogus ampersand.
|
|
{"a & &", 4}, {"a [&", 4},
|
|
}
|
|
|
|
func TestParseError(t *testing.T) {
|
|
for _, tc := range badCases {
|
|
_, err := AsChunk("[test]", tc.src)
|
|
if err == nil {
|
|
t.Errorf("Parse(%q) returns no error", tc.src)
|
|
continue
|
|
}
|
|
posErr0 := err.(MultiError).Entries[0]
|
|
if posErr0.Context.Begin != tc.pos {
|
|
t.Errorf("Parse(%q) first error begins at %d, want %d. Errors are:%s\n", tc.src, posErr0.Context.Begin, tc.pos, err)
|
|
}
|
|
}
|
|
}
|