Made indexing a little more strict
All checks were successful
Build / build (push) Successful in 2m24s
All checks were successful
Build / build (push) Successful in 2m24s
Also added support for var setting with or without the dollar sign.
This commit is contained in:
parent
3a88c0c777
commit
eda791d714
|
@ -98,6 +98,7 @@ type astDotSuffix struct {
|
|||
}
|
||||
|
||||
type astDot struct {
|
||||
Pos lexer.Position
|
||||
Arg astCmdArg `parser:"@@"`
|
||||
DotSuffix []astDotSuffix `parser:"( DOT @@ )*"`
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/participle/v2/lexer"
|
||||
)
|
||||
|
||||
func echoBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
||||
|
@ -509,7 +511,10 @@ func lenBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
|||
return IntObject(0), nil
|
||||
}
|
||||
|
||||
func indexLookup(ctx context.Context, obj, elem Object) (Object, error) {
|
||||
func indexLookup(ctx context.Context, obj, elem Object, pos lexer.Position) (Object, error) {
|
||||
if obj == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch v := obj.(type) {
|
||||
case Listable:
|
||||
intIdx, ok := elem.(IntObject)
|
||||
|
@ -525,9 +530,11 @@ func indexLookup(ctx context.Context, obj, elem Object) (Object, error) {
|
|||
case Hashable:
|
||||
strIdx, ok := elem.(StringObject)
|
||||
if !ok {
|
||||
return nil, errors.New("expected string for Hashable")
|
||||
return nil, nil
|
||||
}
|
||||
return v.Value(string(strIdx)), nil
|
||||
default:
|
||||
return nil, notIndexableError(pos)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -539,7 +546,7 @@ func indexBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
|||
|
||||
val := args.args[0]
|
||||
for _, idx := range args.args[1:] {
|
||||
newVal, err := indexLookup(ctx, val, idx)
|
||||
newVal, err := indexLookup(ctx, val, idx, lexer.Position{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -5,9 +5,10 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type testIterator struct {
|
||||
|
@ -57,6 +58,19 @@ func WithTestBuiltin() InstOption {
|
|||
return &a, nil
|
||||
}))
|
||||
|
||||
i.rootEC.addCmd("rearrange", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
||||
var as ListObject = make([]Object, 0)
|
||||
|
||||
for _, a := range args.args {
|
||||
vs, ok := args.kwargs[a.String()]
|
||||
if ok {
|
||||
as = append(as, vs.Index(0))
|
||||
}
|
||||
}
|
||||
|
||||
return &as, nil
|
||||
}))
|
||||
|
||||
i.rootEC.addCmd("error", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
||||
if len(args.args) == 0 {
|
||||
return nil, errors.New("an error occurred")
|
||||
|
@ -131,10 +145,10 @@ func TestBuiltins_Echo(t *testing.T) {
|
|||
echo "world" # command after this
|
||||
;
|
||||
`, want: "Hello\nworld\n"},
|
||||
{desc: "interpolated string 1", expr: `$what = "world" ; echo "Hello, $what"`, want: "Hello, world\n"},
|
||||
{desc: "interpolated string 2", expr: `$what = "world" ; echo "Hello, \$what"`, want: "Hello, $what\n"},
|
||||
{desc: "interpolated string 1", expr: `what = "world" ; echo "Hello, $what"`, want: "Hello, world\n"},
|
||||
{desc: "interpolated string 2", expr: `what = "world" ; echo "Hello, \$what"`, want: "Hello, $what\n"},
|
||||
{desc: "interpolated string 3", expr: `echo "separate\nlines\n\tand tabs"`, want: "separate\nlines\n\tand tabs\n"},
|
||||
{desc: "interpolated string 4", expr: `$what = "Hello" ; $where = "world" ; echo "$what, $where"`, want: "Hello, world\n"},
|
||||
{desc: "interpolated string 4", expr: `what = "Hello" ; where = "world" ; echo "$what, $where"`, want: "Hello, world\n"},
|
||||
{desc: "interpolated string 5", expr: `
|
||||
for [123 "foo" true ()] { |x|
|
||||
echo "[[$x]]"
|
||||
|
@ -167,19 +181,19 @@ func TestBuiltins_If(t *testing.T) {
|
|||
want string
|
||||
}{
|
||||
{desc: "single then", expr: `
|
||||
$x = "Hello"
|
||||
x = "Hello"
|
||||
if $x {
|
||||
echo "true"
|
||||
}`, want: "true\n(nil)\n"},
|
||||
{desc: "single then and else", expr: `
|
||||
$x = "Hello"
|
||||
x = "Hello"
|
||||
if $x {
|
||||
echo "true"
|
||||
} else {
|
||||
echo "false"
|
||||
}`, want: "true\n(nil)\n"},
|
||||
{desc: "single then, elif and else", expr: `
|
||||
$x = "Hello"
|
||||
x = "Hello"
|
||||
if $y {
|
||||
echo "y is true"
|
||||
} elif $x {
|
||||
|
@ -188,14 +202,14 @@ func TestBuiltins_If(t *testing.T) {
|
|||
echo "nothings x"
|
||||
}`, want: "x is true\n(nil)\n"},
|
||||
{desc: "single then and elif, no else", expr: `
|
||||
$x = "Hello"
|
||||
x = "Hello"
|
||||
if $y {
|
||||
echo "y is true"
|
||||
} elif $x {
|
||||
echo "x is true"
|
||||
}`, want: "x is true\n(nil)\n"},
|
||||
{desc: "single then, two elif, and else", expr: `
|
||||
$x = "Hello"
|
||||
x = "Hello"
|
||||
if $z {
|
||||
echo "z is true"
|
||||
} elif $y {
|
||||
|
@ -213,15 +227,15 @@ func TestBuiltins_If(t *testing.T) {
|
|||
} else {
|
||||
echo "none is true"
|
||||
}`, want: "none is true\n(nil)\n"},
|
||||
{desc: "compressed then", expr: `$x = "Hello" ; if $x { echo "true" }`, want: "true\n(nil)\n"},
|
||||
{desc: "compressed then", expr: `x = "Hello" ; if $x { echo "true" }`, want: "true\n(nil)\n"},
|
||||
{desc: "compressed else", expr: `if $x { echo "true" } else { echo "false" }`, want: "false\n(nil)\n"},
|
||||
{desc: "compressed if", expr: `if $x { echo "x" } elif $y { echo "y" } else { echo "false" }`, want: "false\n(nil)\n"},
|
||||
{desc: "if of itr 1", expr: `$i = itr ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
||||
{desc: "if of itr 2", expr: `$i = itr ; for (seq 1) { head $i } ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
||||
{desc: "if of itr 3", expr: `$i = itr ; for (seq 3) { head $i } ; if $i { echo "more" } else { echo "none" }`, want: "none\n(nil)\n"},
|
||||
{desc: "if of itr 4", expr: `$i = (itr | map { |x| add 2 $x }) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
||||
{desc: "if of itr 5", expr: `$i = (itr | filter { |x| () }) ; if $i { echo "more" } else { echo "none" }`, want: "none\n(nil)\n"},
|
||||
{desc: "if of itr 6", expr: `$i = (itr | filter { |x| 1 }) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
||||
{desc: "if of itr 1", expr: `i = itr ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
||||
{desc: "if of itr 2", expr: `i = itr ; for (seq 1) { head $i } ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
||||
{desc: "if of itr 3", expr: `i = itr ; for (seq 3) { head $i } ; if $i { echo "more" } else { echo "none" }`, want: "none\n(nil)\n"},
|
||||
{desc: "if of itr 4", expr: `i = (itr | map { |x| add 2 $x }) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
||||
{desc: "if of itr 5", expr: `i = (itr | filter { |x| () }) ; if $i { echo "more" } else { echo "none" }`, want: "none\n(nil)\n"},
|
||||
{desc: "if of itr 6", expr: `i = (itr | filter { |x| 1 }) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
@ -508,6 +522,9 @@ func TestBuiltins_Procs(t *testing.T) {
|
|||
echo (call $er ["xxx"])
|
||||
echo (call $er ["yyy"])
|
||||
`, want: "Xxxx\nXxxxyyy\n(nil)\n"},
|
||||
{desc: "calling with kwargs", expr: `
|
||||
echo (call rearrange [b a] [a:"ey" b:"bee"])
|
||||
`, want: "[bee ey]\n(nil)\n"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
|
@ -3,6 +3,7 @@ package ucl
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/alecthomas/participle/v2/lexer"
|
||||
)
|
||||
|
||||
|
@ -12,6 +13,7 @@ var (
|
|||
|
||||
var (
|
||||
tooManyFinallyBlocksError = newBadUsage("try needs at most 1 finally")
|
||||
notIndexableError = newBadUsage("index only support on lists and hashes")
|
||||
)
|
||||
|
||||
type errorWithPos struct {
|
||||
|
|
12
ucl/eval.go
12
ucl/eval.go
|
@ -5,6 +5,8 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/participle/v2/lexer"
|
||||
)
|
||||
|
||||
type evaluator struct {
|
||||
|
@ -205,7 +207,7 @@ func (e evaluator) evalDot(ctx context.Context, ec *evalCtx, n astDot) (Object,
|
|||
}
|
||||
}
|
||||
|
||||
res, err = indexLookup(ctx, res, idx)
|
||||
res, err = indexLookup(ctx, res, idx, n.Pos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -258,9 +260,11 @@ func (e evaluator) evalArg(ctx context.Context, ec *evalCtx, n astCmdArg) (Objec
|
|||
|
||||
func (e evaluator) assignArg(ctx context.Context, ec *evalCtx, n astCmdArg, toVal Object) (Object, error) {
|
||||
switch {
|
||||
case n.Ident != nil:
|
||||
ec.setOrDefineVar(n.Ident.String(), toVal)
|
||||
return toVal, nil
|
||||
case n.Literal != nil:
|
||||
// We may use this for variable setting?
|
||||
return nil, errors.New("cannot assign to a literal")
|
||||
return nil, errors.New("cannot assign to a literal value")
|
||||
case n.Var != nil:
|
||||
ec.setOrDefineVar(*n.Var, toVal)
|
||||
return toVal, nil
|
||||
|
@ -428,7 +432,7 @@ func (e evaluator) interpolateLongIdent(ctx context.Context, ec *evalCtx, n *ast
|
|||
}
|
||||
}
|
||||
|
||||
res, err = indexLookup(ctx, res, idx)
|
||||
res, err = indexLookup(ctx, res, idx, lexer.Position{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
@ -4,19 +4,22 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"ucl.lmika.dev/ucl"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestInst_Eval(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
expr string
|
||||
want any
|
||||
wantObj bool
|
||||
wantErr error
|
||||
desc string
|
||||
expr string
|
||||
want any
|
||||
wantObj bool
|
||||
wantAnErr bool
|
||||
wantErr error
|
||||
}{
|
||||
{desc: "simple string", expr: `firstarg "hello"`, want: "hello"},
|
||||
{desc: "simple int 1", expr: `firstarg 123`, want: 123},
|
||||
|
@ -100,7 +103,14 @@ func TestInst_Eval(t *testing.T) {
|
|||
{desc: "dot idents 6", expr: `$x = [alpha:"hello" bravo:"world"] ; $x.("charlie")`, want: nil},
|
||||
{desc: "dot idents 7", expr: `$x = [MORE:"stuff"] ; $x.("more" | toUpper)`, want: "stuff"},
|
||||
{desc: "dot idents 8", expr: `$x = [MORE:"stuff"] ; $x.(toUpper ("more"))`, want: "stuff"},
|
||||
{desc: "dot idents 9", expr: `$x = [MORE:"stuff"] ; x.y`, want: nil},
|
||||
{desc: "dot idents 9", expr: `$x = [MORE:"stuff"] ; $x.y`, want: nil},
|
||||
|
||||
{desc: "dot err 1", expr: `$x = [1 2 3] ; $x.Hello`, want: nil},
|
||||
{desc: "dot err 2", expr: `$x = [1 2 3] ; $x.("world")`, want: nil},
|
||||
{desc: "dot err 4", expr: `$x = [a:1 b:2] ; $x.(5)`, want: nil},
|
||||
{desc: "dot err 3", expr: `$x = [a:1 b:2] ; $x.(0)`, want: nil},
|
||||
{desc: "dot err 5", expr: `$x = 123 ; $x.(5)`, wantAnErr: true},
|
||||
{desc: "dot err 6", expr: `$x = 123 ; $x.Five`, wantAnErr: true},
|
||||
|
||||
{desc: "parse comments 1", expr: parseComments1, wantObj: true, wantErr: nil},
|
||||
{desc: "parse comments 2", expr: parseComments2, wantObj: true, wantErr: nil},
|
||||
|
@ -118,6 +128,8 @@ func TestInst_Eval(t *testing.T) {
|
|||
|
||||
if tt.wantErr != nil {
|
||||
assert.ErrorIs(t, err, tt.wantErr)
|
||||
} else if tt.wantAnErr {
|
||||
assert.Error(t, err)
|
||||
} else if tt.wantObj {
|
||||
assert.NoError(t, err)
|
||||
_, isObj := res.(ucl.Object)
|
||||
|
|
Loading…
Reference in a new issue