Added lists and hashes

This commit is contained in:
Leon Mika 2024-04-16 22:05:21 +10:00
parent acdd8b3f72
commit ea31e568b6
4 changed files with 130 additions and 6 deletions

View File

@ -11,6 +11,24 @@ type astLiteral struct {
Str *string `parser:"@String"` Str *string `parser:"@String"`
} }
type astHashKey struct {
Literal *astLiteral `parser:"@@"`
Ident *string `parser:"| @Ident"`
Var *string `parser:"| DOLLAR @Ident"`
Sub *astPipeline `parser:"| LP @@ RP"`
}
type astElementPair struct {
Left astCmdArg `parser:"@@"`
Right *astCmdArg `parser:"( COLON @@ )? NL?"`
}
type astListOrHash struct {
EmptyList bool `parser:"@(LS RS)"`
EmptyHash bool `parser:"| @(LS COLON RS)"`
Elements []*astElementPair `parser:"| LS NL? @@+ @@* RS"`
}
type astBlock struct { type astBlock struct {
Statements []*astStatements `parser:"LC NL? @@ NL? RC"` Statements []*astStatements `parser:"LC NL? @@ NL? RC"`
} }
@ -20,6 +38,7 @@ type astCmdArg struct {
Ident *string `parser:"| @Ident"` Ident *string `parser:"| @Ident"`
Var *string `parser:"| DOLLAR @Ident"` Var *string `parser:"| DOLLAR @Ident"`
Sub *astPipeline `parser:"| LP @@ RP"` Sub *astPipeline `parser:"| LP @@ RP"`
ListOrHash *astListOrHash `parser:"| @@"`
Block *astBlock `parser:"| @@"` Block *astBlock `parser:"| @@"`
} }
@ -47,8 +66,11 @@ var scanner = lexer.MustStateful(lexer.Rules{
{"Whitespace", `[ \t]+`, nil}, {"Whitespace", `[ \t]+`, nil},
{"String", `"(\\"|[^"])*"`, nil}, {"String", `"(\\"|[^"])*"`, nil},
{"DOLLAR", `\$`, nil}, {"DOLLAR", `\$`, nil},
{"COLON", `\:`, nil},
{"LP", `\(`, nil}, {"LP", `\(`, nil},
{"RP", `\)`, nil}, {"RP", `\)`, nil},
{"LS", `\[`, nil},
{"RS", `\]`, nil},
{"LC", `\{`, nil}, {"LC", `\{`, nil},
{"RC", `\}`, nil}, {"RC", `\}`, nil},
{"NL", `[;\n][; \n\t]*`, nil}, {"NL", `[;\n][; \n\t]*`, nil},

View File

@ -130,12 +130,57 @@ func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (objec
return nil, nil return nil, nil
case n.Sub != nil: case n.Sub != nil:
return e.evalSub(ctx, ec, n.Sub) return e.evalSub(ctx, ec, n.Sub)
case n.ListOrHash != nil:
return e.evalListOrHash(ctx, ec, n.ListOrHash)
case n.Block != nil: case n.Block != nil:
return blockObject{block: n.Block}, nil return blockObject{block: n.Block}, nil
} }
return nil, errors.New("unhandled arg type") return nil, errors.New("unhandled arg type")
} }
func (e evaluator) evalListOrHash(ctx context.Context, ec *evalCtx, loh *astListOrHash) (object, error) {
if loh.EmptyList {
return listObject{}, nil
} else if loh.EmptyHash {
return hashObject{}, nil
}
if firstIsHash := loh.Elements[0].Right != nil; firstIsHash {
h := hashObject{}
for _, el := range loh.Elements {
if el.Right == nil {
return nil, errors.New("miss-match of lists and hash")
}
n, err := e.evalArg(ctx, ec, el.Left)
if err != nil {
return nil, err
}
v, err := e.evalArg(ctx, ec, *el.Right)
if err != nil {
return nil, err
}
h[n.String()] = v
}
return h, nil
}
l := listObject{}
for _, el := range loh.Elements {
if el.Right != nil {
return nil, errors.New("miss-match of lists and hash")
}
v, err := e.evalArg(ctx, ec, el.Left)
if err != nil {
return nil, err
}
l = append(l, v)
}
return l, nil
}
func (e evaluator) evalLiteral(ctx context.Context, ec *evalCtx, n *astLiteral) (object, error) { func (e evaluator) evalLiteral(ctx context.Context, ec *evalCtx, n *astLiteral) (object, error) {
switch { switch {
case n.Str != nil: case n.Str != nil:

View File

@ -13,7 +13,7 @@ func TestInst_Eval(t *testing.T) {
tests := []struct { tests := []struct {
desc string desc string
expr string expr string
want string want any
}{ }{
{desc: "simple string", expr: `firstarg "hello"`, want: "hello"}, {desc: "simple string", expr: `firstarg "hello"`, want: "hello"},
{desc: "simple ident", expr: `firstarg a-test`, want: "a-test"}, {desc: "simple ident", expr: `firstarg a-test`, want: "a-test"},
@ -39,6 +39,23 @@ func TestInst_Eval(t *testing.T) {
{desc: "multi 1", expr: `firstarg "hello" ; firstarg "world"`, want: "world"}, {desc: "multi 1", expr: `firstarg "hello" ; firstarg "world"`, want: "world"},
{desc: "multi 2", expr: `pipe "hello" | toUpper ; firstarg "world"`, want: "world"}, // TODO: assert for leaks {desc: "multi 2", expr: `pipe "hello" | toUpper ; firstarg "world"`, want: "world"}, // TODO: assert for leaks
{desc: "multi 3", expr: `set new "this is new" ; firstarg $new`, want: "this is new"}, {desc: "multi 3", expr: `set new "this is new" ; firstarg $new`, want: "this is new"},
// Lists
{desc: "list 1", expr: `firstarg ["1" "2" "3"]`, want: []any{"1", "2", "3"}},
{desc: "list 2", expr: `set one "one" ; firstarg [$one (pipe "two" | toUpper) "three"]`, want: []any{"one", "TWO", "three"}},
{desc: "list 3", expr: `firstarg []`, want: []any{}},
// Maps
{desc: "map 1", expr: `firstarg [one:"1" two:"2" three:"3"]`, want: map[string]any{"one": "1", "two": "2", "three": "3"}},
{desc: "map 2", expr: `firstarg ["one":"1" "two":"2" "three":"3"]`, want: map[string]any{"one": "1", "two": "2", "three": "3"}},
{desc: "map 3", expr: `
set one "one" ; set n1 "1"
firstarg [
$one:$n1
(firstarg "two" | toUpper):(firstarg "2" | toUpper)
three:"3"
]`, want: map[string]any{"one": "1", "TWO": "2", "three": "3"}},
{desc: "map 4", expr: `firstarg [:]`, want: map[string]any{}},
} }
for _, tt := range tests { for _, tt := range tests {

View File

@ -12,6 +12,26 @@ type object interface {
Truthy() bool Truthy() bool
} }
type listObject []object
func (s listObject) String() string {
return fmt.Sprintf("%v", []object(s))
}
func (s listObject) Truthy() bool {
return len(s) > 0
}
type hashObject map[string]object
func (s hashObject) String() string {
return fmt.Sprintf("%v", map[string]object(s))
}
func (s hashObject) Truthy() bool {
return len(s) > 0
}
type strObject string type strObject string
func (s strObject) String() string { func (s strObject) String() string {
@ -28,6 +48,26 @@ func toGoValue(obj object) (interface{}, bool) {
return nil, true return nil, true
case strObject: case strObject:
return string(v), true return string(v), true
case listObject:
xs := make([]interface{}, 0, len(v))
for _, va := range v {
x, ok := toGoValue(va)
if !ok {
continue
}
xs = append(xs, x)
}
return xs, true
case hashObject:
xs := make(map[string]interface{})
for k, va := range v {
x, ok := toGoValue(va)
if !ok {
continue
}
xs[k] = x
}
return xs, true
case proxyObject: case proxyObject:
return v.p, true return v.p, true
} }