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:"| @@"`
|
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},
|
||||||
|
|
|
@ -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
|
||||||
|
|
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) {
|
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:
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
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
|
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) {
|
||||||
|
|
Loading…
Reference in a new issue