Added the dot operator

This commit is contained in:
Leon Mika 2024-05-11 09:16:34 +10:00
parent 789add45bb
commit a69fe72f23
5 changed files with 98 additions and 30 deletions

View File

@ -57,9 +57,19 @@ type astCmdArg struct {
Block *astBlock `parser:"| @@"` Block *astBlock `parser:"| @@"`
} }
type astDotSuffix struct {
KeyIdent *astIdentNames `parser:"@@"`
Pipeline *astPipeline `parser:"| LP @@ RP"`
}
type astDot struct {
Arg astCmdArg `parser:"@@"`
DotSuffix []astDotSuffix `parser:"( DOT @@ )*"`
}
type astCmd struct { type astCmd struct {
Name astCmdArg `parser:"@@"` Name astDot `parser:"@@"`
Args []astCmdArg `parser:"@@*"` Args []astDot `parser:"@@*"`
} }
type astPipeline struct { type astPipeline struct {
@ -84,6 +94,7 @@ var scanner = lexer.MustStateful(lexer.Rules{
{"Int", `[-]?[0-9][0-9]*`, nil}, {"Int", `[-]?[0-9][0-9]*`, nil},
{"DOLLAR", `\$`, nil}, {"DOLLAR", `\$`, nil},
{"COLON", `\:`, nil}, {"COLON", `\:`, nil},
{"DOT", `[.]`, nil},
{"LP", `\(`, nil}, {"LP", `\(`, nil},
{"RP", `\)`, nil}, {"RP", `\)`, nil},
{"LS", `\[`, nil}, {"LS", `\[`, nil},

View File

@ -158,6 +158,27 @@ func lenBuiltin(ctx context.Context, args invocationArgs) (object, error) {
return intObject(0), nil return intObject(0), nil
} }
func indexLookup(ctx context.Context, obj, elem object) (object, error) {
switch v := obj.(type) {
case listable:
intIdx, ok := elem.(intObject)
if !ok {
return nil, nil
}
if int(intIdx) >= 0 && int(intIdx) < v.Len() {
return v.Index(int(intIdx)), nil
}
return nil, nil
case hashable:
strIdx, ok := elem.(strObject)
if !ok {
return nil, errors.New("expected string for hashable")
}
return v.Value(string(strIdx)), nil
}
return nil, nil
}
func indexBuiltin(ctx context.Context, args invocationArgs) (object, error) { func indexBuiltin(ctx context.Context, args invocationArgs) (object, error) {
if err := args.expectArgn(1); err != nil { if err := args.expectArgn(1); err != nil {
return nil, err return nil, err
@ -165,26 +186,11 @@ func indexBuiltin(ctx context.Context, args invocationArgs) (object, error) {
val := args.args[0] val := args.args[0]
for _, idx := range args.args[1:] { for _, idx := range args.args[1:] {
switch v := val.(type) { newVal, err := indexLookup(ctx, val, idx)
case listable: if err != nil {
intIdx, ok := idx.(intObject) return nil, err
if !ok {
return nil, nil
}
if int(intIdx) >= 0 && int(intIdx) < v.Len() {
val = v.Index(int(intIdx))
} else {
val = nil
}
case hashable:
strIdx, ok := idx.(strObject)
if !ok {
return nil, errors.New("expected string for hashable")
}
val = v.Value(string(strIdx))
default:
return val, nil
} }
val = newVal
} }
return val, nil return val, nil

View File

@ -71,8 +71,8 @@ func (e evaluator) evalPipeline(ctx context.Context, ec *evalCtx, n *astPipeline
func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe object, ast *astCmd) (object, error) { func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe object, ast *astCmd) (object, error) {
switch { switch {
case ast.Name.Ident != nil: case (ast.Name.Arg.Ident != nil) && len(ast.Name.DotSuffix) == 0:
name := ast.Name.Ident.String() name := ast.Name.Arg.Ident.String()
// Regular command // Regular command
if cmd := ec.lookupInvokable(name); cmd != nil { if cmd := ec.lookupInvokable(name); cmd != nil {
@ -85,7 +85,7 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe object,
return nil, errors.New("unknown command: " + name) return nil, errors.New("unknown command: " + name)
} }
case len(ast.Args) > 0: case len(ast.Args) > 0:
nameElem, err := e.evalArg(ctx, ec, ast.Name) nameElem, err := e.evalDot(ctx, ec, ast.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -98,7 +98,7 @@ func (e evaluator) evalCmd(ctx context.Context, ec *evalCtx, currentPipe object,
return e.evalInvokable(ctx, ec, currentPipe, ast, inv) return e.evalInvokable(ctx, ec, currentPipe, ast, inv)
} }
nameElem, err := e.evalArg(ctx, ec, ast.Name) nameElem, err := e.evalDot(ctx, ec, ast.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -117,7 +117,7 @@ func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe o
argsPtr.Append(currentPipe) argsPtr.Append(currentPipe)
} }
for _, arg := range ast.Args { for _, arg := range ast.Args {
if ident := arg.Ident; ident != nil && ident.String()[0] == '-' { if ident := arg.Arg.Ident; len(arg.DotSuffix) == 0 && ident != nil && ident.String()[0] == '-' {
// Arg switch // Arg switch
if kwargs == nil { if kwargs == nil {
kwargs = make(map[string]*listObject) kwargs = make(map[string]*listObject)
@ -126,7 +126,7 @@ func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe o
argsPtr = &listObject{} argsPtr = &listObject{}
kwargs[ident.String()[1:]] = argsPtr kwargs[ident.String()[1:]] = argsPtr
} else { } else {
ae, err := e.evalArg(ctx, ec, arg) ae, err := e.evalDot(ctx, ec, arg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -148,6 +148,33 @@ func (e evaluator) evalMacro(ctx context.Context, ec *evalCtx, hasPipe bool, pip
}) })
} }
func (e evaluator) evalDot(ctx context.Context, ec *evalCtx, n astDot) (object, error) {
res, err := e.evalArg(ctx, ec, n.Arg)
if err != nil {
return nil, err
} else if len(n.DotSuffix) == 0 {
return res, nil
}
for _, dot := range n.DotSuffix {
var idx object
if dot.KeyIdent != nil {
idx = strObject(dot.KeyIdent.String())
} else {
idx, err = e.evalPipeline(ctx, ec, dot.Pipeline)
if err != nil {
return nil, err
}
}
res, err = indexLookup(ctx, res, idx)
if err != nil {
return nil, err
}
}
return res, nil
}
func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (object, error) { func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (object, error) {
switch { switch {
case n.Literal != nil: case n.Literal != nil:

View File

@ -60,6 +60,22 @@ func TestInst_Eval(t *testing.T) {
three:"3" three:"3"
]`, want: map[string]any{"one": "1", "TWO": "2", "three": "3"}}, ]`, want: map[string]any{"one": "1", "TWO": "2", "three": "3"}},
{desc: "map 4", expr: `firstarg [:]`, want: map[string]any{}}, {desc: "map 4", expr: `firstarg [:]`, want: map[string]any{}},
// Dots
{desc: "dot 1", expr: `set x [1 2 3] ; $x.(0)`, want: 1},
{desc: "dot 2", expr: `set x [1 2 3] ; $x.(1)`, want: 2},
{desc: "dot 3", expr: `set x [1 2 3] ; $x.(2)`, want: 3},
{desc: "dot 4", expr: `set x [1 2 3] ; $x.(3)`, want: nil},
{desc: "dot 5", expr: `set x [1 2 3] ; $x.(add 1 1)`, want: 3},
{desc: "dot 6", expr: `set x [alpha:"hello" bravo:"world"] ; $x.alpha`, want: "hello"},
{desc: "dot 7", expr: `set x [alpha:"hello" bravo:"world"] ; $x.bravo`, want: "world"},
{desc: "dot 8", expr: `set x [alpha:"hello" bravo:"world"] ; $x.charlie`, want: nil},
{desc: "dot 9", expr: `set x [alpha:"hello" bravo:"world"] ; $x.("alpha")`, want: "hello"},
{desc: "dot 10", expr: `set x [alpha:"hello" bravo:"world"] ; $x.("bravo")`, want: "world"},
{desc: "dot 11", expr: `set x [alpha:"hello" bravo:"world"] ; $x.("charlie")`, want: nil},
{desc: "dot 12", expr: `set x [MORE:"stuff"] ; $x.("more" | toUpper)`, want: "stuff"},
{desc: "dot 13", expr: `set x [MORE:"stuff"] ; $x.(toUpper ("more"))`, want: "stuff"},
{desc: "dot 14", expr: `set x [MORE:"stuff"] ; x.y`, want: nil},
} }
for _, tt := range tests { for _, tt := range tests {

View File

@ -189,7 +189,11 @@ func (ma macroArgs) identIs(ctx context.Context, n int, expectedIdent string) bo
return false return false
} }
lit := ma.ast.Args[ma.argShift+n].Ident if len(ma.ast.Args[ma.argShift+n].DotSuffix) != 0 {
return false
}
lit := ma.ast.Args[ma.argShift+n].Arg.Ident
if lit == nil { if lit == nil {
return false return false
} }
@ -202,7 +206,11 @@ func (ma *macroArgs) shiftIdent(ctx context.Context) (string, bool) {
return "", false return "", false
} }
lit := ma.ast.Args[ma.argShift].Ident if len(ma.ast.Args[ma.argShift].DotSuffix) != 0 {
return "", false
}
lit := ma.ast.Args[ma.argShift].Arg.Ident
if lit != nil { if lit != nil {
ma.argShift += 1 ma.argShift += 1
return lit.String(), true return lit.String(), true
@ -215,7 +223,7 @@ func (ma macroArgs) evalArg(ctx context.Context, n int) (object, error) {
return nil, errors.New("not enough arguments") // FIX return nil, errors.New("not enough arguments") // FIX
} }
return ma.eval.evalArg(ctx, ma.ec, ma.ast.Args[ma.argShift+n]) return ma.eval.evalDot(ctx, ma.ec, ma.ast.Args[ma.argShift+n])
} }
func (ma macroArgs) evalBlock(ctx context.Context, n int, args []object, pushScope bool) (object, error) { func (ma macroArgs) evalBlock(ctx context.Context, n int, args []object, pushScope bool) (object, error) {