Added the dot operator
This commit is contained in:
parent
789add45bb
commit
a69fe72f23
15
ucl/ast.go
15
ucl/ast.go
|
@ -57,9 +57,19 @@ type astCmdArg struct {
|
|||
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 {
|
||||
Name astCmdArg `parser:"@@"`
|
||||
Args []astCmdArg `parser:"@@*"`
|
||||
Name astDot `parser:"@@"`
|
||||
Args []astDot `parser:"@@*"`
|
||||
}
|
||||
|
||||
type astPipeline struct {
|
||||
|
@ -84,6 +94,7 @@ var scanner = lexer.MustStateful(lexer.Rules{
|
|||
{"Int", `[-]?[0-9][0-9]*`, nil},
|
||||
{"DOLLAR", `\$`, nil},
|
||||
{"COLON", `\:`, nil},
|
||||
{"DOT", `[.]`, nil},
|
||||
{"LP", `\(`, nil},
|
||||
{"RP", `\)`, nil},
|
||||
{"LS", `\[`, nil},
|
||||
|
|
|
@ -158,6 +158,27 @@ func lenBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
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) {
|
||||
if err := args.expectArgn(1); err != nil {
|
||||
return nil, err
|
||||
|
@ -165,26 +186,11 @@ func indexBuiltin(ctx context.Context, args invocationArgs) (object, error) {
|
|||
|
||||
val := args.args[0]
|
||||
for _, idx := range args.args[1:] {
|
||||
switch v := val.(type) {
|
||||
case listable:
|
||||
intIdx, ok := idx.(intObject)
|
||||
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
|
||||
newVal, err := indexLookup(ctx, val, idx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
val = newVal
|
||||
}
|
||||
|
||||
return val, nil
|
||||
|
|
39
ucl/eval.go
39
ucl/eval.go
|
@ -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) {
|
||||
switch {
|
||||
case ast.Name.Ident != nil:
|
||||
name := ast.Name.Ident.String()
|
||||
case (ast.Name.Arg.Ident != nil) && len(ast.Name.DotSuffix) == 0:
|
||||
name := ast.Name.Arg.Ident.String()
|
||||
|
||||
// Regular command
|
||||
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)
|
||||
}
|
||||
case len(ast.Args) > 0:
|
||||
nameElem, err := e.evalArg(ctx, ec, ast.Name)
|
||||
nameElem, err := e.evalDot(ctx, ec, ast.Name)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
nameElem, err := e.evalArg(ctx, ec, ast.Name)
|
||||
nameElem, err := e.evalDot(ctx, ec, ast.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe o
|
|||
argsPtr.Append(currentPipe)
|
||||
}
|
||||
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
|
||||
if kwargs == nil {
|
||||
kwargs = make(map[string]*listObject)
|
||||
|
@ -126,7 +126,7 @@ func (e evaluator) evalInvokable(ctx context.Context, ec *evalCtx, currentPipe o
|
|||
argsPtr = &listObject{}
|
||||
kwargs[ident.String()[1:]] = argsPtr
|
||||
} else {
|
||||
ae, err := e.evalArg(ctx, ec, arg)
|
||||
ae, err := e.evalDot(ctx, ec, arg)
|
||||
if err != nil {
|
||||
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) {
|
||||
switch {
|
||||
case n.Literal != nil:
|
||||
|
|
|
@ -60,6 +60,22 @@ func TestInst_Eval(t *testing.T) {
|
|||
three:"3"
|
||||
]`, want: map[string]any{"one": "1", "TWO": "2", "three": "3"}},
|
||||
{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 {
|
||||
|
|
14
ucl/objs.go
14
ucl/objs.go
|
@ -189,7 +189,11 @@ func (ma macroArgs) identIs(ctx context.Context, n int, expectedIdent string) bo
|
|||
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 {
|
||||
return false
|
||||
}
|
||||
|
@ -202,7 +206,11 @@ func (ma *macroArgs) shiftIdent(ctx context.Context) (string, bool) {
|
|||
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 {
|
||||
ma.argShift += 1
|
||||
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 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) {
|
||||
|
|
Loading…
Reference in a new issue