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 {
|
type astDot struct {
|
||||||
|
Pos lexer.Position
|
||||||
Arg astCmdArg `parser:"@@"`
|
Arg astCmdArg `parser:"@@"`
|
||||||
DotSuffix []astDotSuffix `parser:"( DOT @@ )*"`
|
DotSuffix []astDotSuffix `parser:"( DOT @@ )*"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alecthomas/participle/v2/lexer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func echoBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
|
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
|
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) {
|
switch v := obj.(type) {
|
||||||
case Listable:
|
case Listable:
|
||||||
intIdx, ok := elem.(IntObject)
|
intIdx, ok := elem.(IntObject)
|
||||||
|
@ -525,9 +530,11 @@ func indexLookup(ctx context.Context, obj, elem Object) (Object, error) {
|
||||||
case Hashable:
|
case Hashable:
|
||||||
strIdx, ok := elem.(StringObject)
|
strIdx, ok := elem.(StringObject)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("expected string for Hashable")
|
return nil, nil
|
||||||
}
|
}
|
||||||
return v.Value(string(strIdx)), nil
|
return v.Value(string(strIdx)), nil
|
||||||
|
default:
|
||||||
|
return nil, notIndexableError(pos)
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -539,7 +546,7 @@ 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:] {
|
||||||
newVal, err := indexLookup(ctx, val, idx)
|
newVal, err := indexLookup(ctx, val, idx, lexer.Position{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,10 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testIterator struct {
|
type testIterator struct {
|
||||||
|
@ -57,6 +58,19 @@ func WithTestBuiltin() InstOption {
|
||||||
return &a, nil
|
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) {
|
i.rootEC.addCmd("error", invokableFunc(func(ctx context.Context, args invocationArgs) (Object, error) {
|
||||||
if len(args.args) == 0 {
|
if len(args.args) == 0 {
|
||||||
return nil, errors.New("an error occurred")
|
return nil, errors.New("an error occurred")
|
||||||
|
@ -131,10 +145,10 @@ func TestBuiltins_Echo(t *testing.T) {
|
||||||
echo "world" # command after this
|
echo "world" # command after this
|
||||||
;
|
;
|
||||||
`, want: "Hello\nworld\n"},
|
`, want: "Hello\nworld\n"},
|
||||||
{desc: "interpolated string 1", expr: `$what = "world" ; echo "Hello, $what"`, want: "Hello, world\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 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 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: `
|
{desc: "interpolated string 5", expr: `
|
||||||
for [123 "foo" true ()] { |x|
|
for [123 "foo" true ()] { |x|
|
||||||
echo "[[$x]]"
|
echo "[[$x]]"
|
||||||
|
@ -167,19 +181,19 @@ func TestBuiltins_If(t *testing.T) {
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{desc: "single then", expr: `
|
{desc: "single then", expr: `
|
||||||
$x = "Hello"
|
x = "Hello"
|
||||||
if $x {
|
if $x {
|
||||||
echo "true"
|
echo "true"
|
||||||
}`, want: "true\n(nil)\n"},
|
}`, want: "true\n(nil)\n"},
|
||||||
{desc: "single then and else", expr: `
|
{desc: "single then and else", expr: `
|
||||||
$x = "Hello"
|
x = "Hello"
|
||||||
if $x {
|
if $x {
|
||||||
echo "true"
|
echo "true"
|
||||||
} else {
|
} else {
|
||||||
echo "false"
|
echo "false"
|
||||||
}`, want: "true\n(nil)\n"},
|
}`, want: "true\n(nil)\n"},
|
||||||
{desc: "single then, elif and else", expr: `
|
{desc: "single then, elif and else", expr: `
|
||||||
$x = "Hello"
|
x = "Hello"
|
||||||
if $y {
|
if $y {
|
||||||
echo "y is true"
|
echo "y is true"
|
||||||
} elif $x {
|
} elif $x {
|
||||||
|
@ -188,14 +202,14 @@ func TestBuiltins_If(t *testing.T) {
|
||||||
echo "nothings x"
|
echo "nothings x"
|
||||||
}`, want: "x is true\n(nil)\n"},
|
}`, want: "x is true\n(nil)\n"},
|
||||||
{desc: "single then and elif, no else", expr: `
|
{desc: "single then and elif, no else", expr: `
|
||||||
$x = "Hello"
|
x = "Hello"
|
||||||
if $y {
|
if $y {
|
||||||
echo "y is true"
|
echo "y is true"
|
||||||
} elif $x {
|
} elif $x {
|
||||||
echo "x is true"
|
echo "x is true"
|
||||||
}`, want: "x is true\n(nil)\n"},
|
}`, want: "x is true\n(nil)\n"},
|
||||||
{desc: "single then, two elif, and else", expr: `
|
{desc: "single then, two elif, and else", expr: `
|
||||||
$x = "Hello"
|
x = "Hello"
|
||||||
if $z {
|
if $z {
|
||||||
echo "z is true"
|
echo "z is true"
|
||||||
} elif $y {
|
} elif $y {
|
||||||
|
@ -213,15 +227,15 @@ func TestBuiltins_If(t *testing.T) {
|
||||||
} else {
|
} else {
|
||||||
echo "none is true"
|
echo "none is true"
|
||||||
}`, want: "none is true\n(nil)\n"},
|
}`, 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 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: "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 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 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 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 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 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 6", expr: `i = (itr | filter { |x| 1 }) ; if $i { echo "more" } else { echo "none" }`, want: "more\n(nil)\n"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -508,6 +522,9 @@ func TestBuiltins_Procs(t *testing.T) {
|
||||||
echo (call $er ["xxx"])
|
echo (call $er ["xxx"])
|
||||||
echo (call $er ["yyy"])
|
echo (call $er ["yyy"])
|
||||||
`, want: "Xxxx\nXxxxyyy\n(nil)\n"},
|
`, 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 {
|
for _, tt := range tests {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package ucl
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/alecthomas/participle/v2/lexer"
|
"github.com/alecthomas/participle/v2/lexer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -12,6 +13,7 @@ var (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
tooManyFinallyBlocksError = newBadUsage("try needs at most 1 finally")
|
tooManyFinallyBlocksError = newBadUsage("try needs at most 1 finally")
|
||||||
|
notIndexableError = newBadUsage("index only support on lists and hashes")
|
||||||
)
|
)
|
||||||
|
|
||||||
type errorWithPos struct {
|
type errorWithPos struct {
|
||||||
|
|
12
ucl/eval.go
12
ucl/eval.go
|
@ -5,6 +5,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alecthomas/participle/v2/lexer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type evaluator struct {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
func (e evaluator) assignArg(ctx context.Context, ec *evalCtx, n astCmdArg, toVal Object) (Object, error) {
|
||||||
switch {
|
switch {
|
||||||
|
case n.Ident != nil:
|
||||||
|
ec.setOrDefineVar(n.Ident.String(), toVal)
|
||||||
|
return toVal, nil
|
||||||
case n.Literal != nil:
|
case n.Literal != nil:
|
||||||
// We may use this for variable setting?
|
return nil, errors.New("cannot assign to a literal value")
|
||||||
return nil, errors.New("cannot assign to a literal")
|
|
||||||
case n.Var != nil:
|
case n.Var != nil:
|
||||||
ec.setOrDefineVar(*n.Var, toVal)
|
ec.setOrDefineVar(*n.Var, toVal)
|
||||||
return toVal, nil
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,19 +4,22 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"ucl.lmika.dev/ucl"
|
"ucl.lmika.dev/ucl"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInst_Eval(t *testing.T) {
|
func TestInst_Eval(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
desc string
|
desc string
|
||||||
expr string
|
expr string
|
||||||
want any
|
want any
|
||||||
wantObj bool
|
wantObj bool
|
||||||
wantErr error
|
wantAnErr bool
|
||||||
|
wantErr error
|
||||||
}{
|
}{
|
||||||
{desc: "simple string", expr: `firstarg "hello"`, want: "hello"},
|
{desc: "simple string", expr: `firstarg "hello"`, want: "hello"},
|
||||||
{desc: "simple int 1", expr: `firstarg 123`, want: 123},
|
{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 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 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 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 1", expr: parseComments1, wantObj: true, wantErr: nil},
|
||||||
{desc: "parse comments 2", expr: parseComments2, 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 {
|
if tt.wantErr != nil {
|
||||||
assert.ErrorIs(t, err, tt.wantErr)
|
assert.ErrorIs(t, err, tt.wantErr)
|
||||||
|
} else if tt.wantAnErr {
|
||||||
|
assert.Error(t, err)
|
||||||
} else if tt.wantObj {
|
} else if tt.wantObj {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
_, isObj := res.(ucl.Object)
|
_, isObj := res.(ucl.Object)
|
||||||
|
|
Loading…
Reference in a new issue