Compare commits
2 commits
eda791d714
...
e2f471c608
Author | SHA1 | Date | |
---|---|---|---|
|
e2f471c608 | ||
|
2172fb86d8 |
|
@ -539,6 +539,39 @@ func indexLookup(ctx context.Context, obj, elem Object, pos lexer.Position) (Obj
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func indexAssign(ctx context.Context, obj, elem, toVal Object, pos lexer.Position) (_ Object, err error) {
|
||||||
|
if obj == nil {
|
||||||
|
return nil, assignToNilIndex(pos)
|
||||||
|
}
|
||||||
|
switch v := obj.(type) {
|
||||||
|
case ModListable:
|
||||||
|
intIdx, ok := elem.(IntObject)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if int(intIdx) >= 0 && int(intIdx) < v.Len() {
|
||||||
|
err = v.SetIndex(int(intIdx), toVal)
|
||||||
|
} else if int(intIdx) < 0 && int(intIdx) >= -v.Len() {
|
||||||
|
err = v.SetIndex(v.Len()+int(intIdx), toVal)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return toVal, nil
|
||||||
|
case ModHashable:
|
||||||
|
strIdx, ok := elem.(StringObject)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
err = v.SetValue(string(strIdx), toVal)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return toVal, nil
|
||||||
|
}
|
||||||
|
return nil, notModIndexableError(pos)
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -295,53 +295,53 @@ func TestBuiltins_While(t *testing.T) {
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{desc: "iterate while true 1", expr: `
|
{desc: "iterate while true 1", expr: `
|
||||||
$x = 0
|
x = 0
|
||||||
while (lt $x 5) {
|
while (lt $x 5) {
|
||||||
echo $x
|
echo $x
|
||||||
$x = (add $x 1)
|
x = add $x 1
|
||||||
}
|
}
|
||||||
echo "done"`, want: "0\n1\n2\n3\n4\ndone\n(nil)\n"},
|
echo "done"`, want: "0\n1\n2\n3\n4\ndone\n(nil)\n"},
|
||||||
{desc: "iterate while true 2", expr: `
|
{desc: "iterate while true 2", expr: `
|
||||||
$x = 20
|
x = 20
|
||||||
while (lt $x 5) {
|
while (lt $x 5) {
|
||||||
echo $x
|
echo $x
|
||||||
$x = (add $x 1)
|
x = (add $x 1)
|
||||||
}
|
}
|
||||||
echo "done"`, want: "done\n(nil)\n"},
|
echo "done"`, want: "done\n(nil)\n"},
|
||||||
{desc: "iterate while true with pipeline", expr: `
|
{desc: "iterate while true with pipeline", expr: `
|
||||||
$x = 0
|
x = 0
|
||||||
while (lt $x 5) {
|
while (lt $x 5) {
|
||||||
echo $x
|
echo $x
|
||||||
$x = (add $x 1)
|
x = (add $x 1)
|
||||||
if (ge $x 3) {
|
if (ge $x 3) {
|
||||||
break "Ahh"
|
break "Ahh"
|
||||||
}
|
}
|
||||||
} | echo " was the break"
|
} | echo " was the break"
|
||||||
echo "done"`, want: "0\n1\n2\nAhh was the break\ndone\n(nil)\n"},
|
echo "done"`, want: "0\n1\n2\nAhh was the break\ndone\n(nil)\n"},
|
||||||
{desc: "iterate for ever with break 1", expr: `
|
{desc: "iterate for ever with break 1", expr: `
|
||||||
$x = 0
|
x = 0
|
||||||
while {
|
while {
|
||||||
echo $x
|
echo $x
|
||||||
$x = (add $x 1)
|
x = add $x 1
|
||||||
if (ge $x 5) {
|
if (ge $x 5) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
echo "done"`, want: "0\n1\n2\n3\n4\ndone\n(nil)\n"},
|
echo "done"`, want: "0\n1\n2\n3\n4\ndone\n(nil)\n"},
|
||||||
{desc: "iterate for ever with break 2", expr: `
|
{desc: "iterate for ever with break 2", expr: `
|
||||||
$x = 0
|
x = 0
|
||||||
echo (while {
|
echo (while {
|
||||||
echo $x
|
echo $x
|
||||||
$x = add $x 1
|
x = add $x 1
|
||||||
if (ge $x 5) {
|
if (ge $x 5) {
|
||||||
break $x
|
break $x
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
`, want: "0\n1\n2\n3\n4\n5\n(nil)\n"},
|
`, want: "0\n1\n2\n3\n4\n5\n(nil)\n"},
|
||||||
{desc: "iterate for ever with continue", expr: `
|
{desc: "iterate for ever with continue", expr: `
|
||||||
$x = 0
|
x = 0
|
||||||
while {
|
while {
|
||||||
$x = (add $x 1)
|
x = (add $x 1)
|
||||||
if (or (eq $x 2) (eq $x 4)) {
|
if (or (eq $x 2) (eq $x 4)) {
|
||||||
echo "quack"
|
echo "quack"
|
||||||
continue
|
continue
|
||||||
|
@ -501,10 +501,10 @@ func TestBuiltins_Procs(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$helloGreater = makeGreeter "Hello"
|
helloGreater = makeGreeter "Hello"
|
||||||
$helloGreater "world"
|
$helloGreater "world"
|
||||||
|
|
||||||
$goodbye = makeGreeter "Goodbye cruel"
|
goodbye = makeGreeter "Goodbye cruel"
|
||||||
$goodbye "world"
|
$goodbye "world"
|
||||||
|
|
||||||
call (makeGreeter "Quick") ["call me"]
|
call (makeGreeter "Quick") ["call me"]
|
||||||
|
@ -512,13 +512,13 @@ func TestBuiltins_Procs(t *testing.T) {
|
||||||
`, want: "Hello, world\nGoodbye cruel, world\nQuick, call me\n(nil)\n"},
|
`, want: "Hello, world\nGoodbye cruel, world\nQuick, call me\n(nil)\n"},
|
||||||
{desc: "modifying closed over variables", expr: `
|
{desc: "modifying closed over variables", expr: `
|
||||||
proc makeSetter {
|
proc makeSetter {
|
||||||
$bla = "X"
|
bla = "X"
|
||||||
proc appendToBla { |x|
|
proc appendToBla { |x|
|
||||||
$bla = cat $bla $x
|
bla = cat $bla $x
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$er = makeSetter
|
er = makeSetter
|
||||||
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"},
|
||||||
|
@ -623,7 +623,7 @@ func TestBuiltins_Return(t *testing.T) {
|
||||||
echo "world"
|
echo "world"
|
||||||
}
|
}
|
||||||
proc greet {
|
proc greet {
|
||||||
$what = (greetWhat)
|
what = (greetWhat)
|
||||||
echo "Hello, " $what
|
echo "Hello, " $what
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -687,7 +687,7 @@ func TestBuiltins_Return(t *testing.T) {
|
||||||
|
|
||||||
proc test-thing {
|
proc test-thing {
|
||||||
for [1 2 3] { |x|
|
for [1 2 3] { |x|
|
||||||
$myClosure = proc { echo $x }
|
myClosure = proc { echo $x }
|
||||||
do-thing $myClosure
|
do-thing $myClosure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -714,12 +714,12 @@ func TestBuiltins_Return(t *testing.T) {
|
||||||
|
|
||||||
proc test-thing {
|
proc test-thing {
|
||||||
[1 2 3] | map { |x|
|
[1 2 3] | map { |x|
|
||||||
$myProc = proc { echo $x }
|
myProc = proc { echo $x }
|
||||||
proc { do-thing $myProc }
|
proc { do-thing $myProc }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$hello = "xx"
|
hello = "xx"
|
||||||
for (test-thing) { |y| call $y ; echo $hello }
|
for (test-thing) { |y| call $y ; echo $hello }
|
||||||
`, want: "1\nxx\n2\nxx\n3\nxx\n(nil)\n"},
|
`, want: "1\nxx\n2\nxx\n3\nxx\n(nil)\n"},
|
||||||
{desc: "check closure 7", expr: `
|
{desc: "check closure 7", expr: `
|
||||||
|
@ -728,15 +728,15 @@ func TestBuiltins_Return(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
proc test-thing {
|
proc test-thing {
|
||||||
$f = 0
|
f = 0
|
||||||
[1 2 3] | map { |x|
|
[1 2 3] | map { |x|
|
||||||
$myProc = proc { echo $f }
|
myProc = proc { echo $f }
|
||||||
$f = (add $f 1)
|
f = (add $f 1)
|
||||||
proc { do-thing $myProc }
|
proc { do-thing $myProc }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$hello = "xx"
|
hello = "xx"
|
||||||
for (test-thing) { |y| call $y ; echo $hello }
|
for (test-thing) { |y| call $y ; echo $hello }
|
||||||
`, want: "3\nxx\n3\nxx\n3\nxx\n(nil)\n"},
|
`, want: "3\nxx\n3\nxx\n3\nxx\n(nil)\n"},
|
||||||
{desc: "check closure 7", expr: `
|
{desc: "check closure 7", expr: `
|
||||||
|
@ -745,16 +745,16 @@ func TestBuiltins_Return(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
proc test-thing {
|
proc test-thing {
|
||||||
$f = 1
|
f = 1
|
||||||
[1 2 3] | map { |x|
|
[1 2 3] | map { |x|
|
||||||
$g = $f
|
g = $f
|
||||||
$myProc = (proc { echo $g })
|
myProc = (proc { echo $g })
|
||||||
$f = (add $f 1)
|
f = (add $f 1)
|
||||||
proc { do-thing $myProc }
|
proc { do-thing $myProc }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$hello = "xx"
|
hello = "xx"
|
||||||
for (test-thing) { |y| call $y ; echo $hello }
|
for (test-thing) { |y| call $y ; echo $hello }
|
||||||
`, want: "1\nxx\n2\nxx\n3\nxx\n(nil)\n"},
|
`, want: "1\nxx\n2\nxx\n3\nxx\n(nil)\n"},
|
||||||
}
|
}
|
||||||
|
@ -917,11 +917,11 @@ func TestBuiltins_Try(t *testing.T) {
|
||||||
}
|
}
|
||||||
`, want: "Hello\nCatch me: bang\nAlways\n", wantErr: "boom"},
|
`, want: "Hello\nCatch me: bang\nAlways\n", wantErr: "boom"},
|
||||||
{desc: "try 12", expr: `
|
{desc: "try 12", expr: `
|
||||||
$a = try { "e" } catch { "f" }
|
a = try { "e" } catch { "f" }
|
||||||
echo $a
|
echo $a
|
||||||
`, want: "e\n(nil)\n"},
|
`, want: "e\n(nil)\n"},
|
||||||
{desc: "try 13", expr: `
|
{desc: "try 13", expr: `
|
||||||
$a = try { error "bang" } catch { "f" }
|
a = try { error "bang" } catch { "f" }
|
||||||
echo $a
|
echo $a
|
||||||
`, want: "f\n(nil)\n"},
|
`, want: "f\n(nil)\n"},
|
||||||
{desc: "try 14", expr: `
|
{desc: "try 14", expr: `
|
||||||
|
@ -1081,12 +1081,12 @@ func TestBuiltins_Map(t *testing.T) {
|
||||||
map ["a" "b" "c"] (proc { |x| makeUpper $x })
|
map ["a" "b" "c"] (proc { |x| makeUpper $x })
|
||||||
`, want: "A\nB\nC\n"},
|
`, want: "A\nB\nC\n"},
|
||||||
{desc: "map list 2", expr: `
|
{desc: "map list 2", expr: `
|
||||||
$makeUpper = proc { |x| $x | toUpper }
|
makeUpper = proc { |x| $x | toUpper }
|
||||||
|
|
||||||
map ["a" "b" "c"] $makeUpper
|
map ["a" "b" "c"] $makeUpper
|
||||||
`, want: "A\nB\nC\n"},
|
`, want: "A\nB\nC\n"},
|
||||||
{desc: "map list with pipe", expr: `
|
{desc: "map list with pipe", expr: `
|
||||||
$makeUpper = proc { |x| $x | toUpper }
|
makeUpper = proc { |x| $x | toUpper }
|
||||||
|
|
||||||
["a" "b" "c"] | map $makeUpper
|
["a" "b" "c"] | map $makeUpper
|
||||||
`, want: "A\nB\nC\n"},
|
`, want: "A\nB\nC\n"},
|
||||||
|
@ -1094,15 +1094,15 @@ func TestBuiltins_Map(t *testing.T) {
|
||||||
map ["a" "b" "c"] { |x| toUpper $x }
|
map ["a" "b" "c"] { |x| toUpper $x }
|
||||||
`, want: "A\nB\nC\n"},
|
`, want: "A\nB\nC\n"},
|
||||||
{desc: "map list with stream", expr: `
|
{desc: "map list with stream", expr: `
|
||||||
$makeUpper = proc { |x| toUpper $x }
|
makeUpper = proc { |x| toUpper $x }
|
||||||
|
|
||||||
$l = ["a" "b" "c"] | map $makeUpper
|
l = ["a" "b" "c"] | map $makeUpper
|
||||||
echo $l
|
echo $l
|
||||||
`, want: "[A B C]\n(nil)\n"},
|
`, want: "[A B C]\n(nil)\n"},
|
||||||
{desc: "map itr stream", expr: `
|
{desc: "map itr stream", expr: `
|
||||||
$add2 = proc { |x| add $x 2 }
|
add2 = proc { |x| add $x 2 }
|
||||||
|
|
||||||
$l = itr | map $add2
|
l = itr | map $add2
|
||||||
for $l { |x| echo $x }
|
for $l { |x| echo $x }
|
||||||
`, want: "3\n4\n5\n(nil)\n"},
|
`, want: "3\n4\n5\n(nil)\n"},
|
||||||
}
|
}
|
||||||
|
@ -1356,7 +1356,7 @@ func TestBuiltins_Filter(t *testing.T) {
|
||||||
}},
|
}},
|
||||||
{desc: "filter map 3", expr: `filter [alpha:"hello" bravo:"world"] { |k v| eq $v "alpha" }`, want: map[string]any{}},
|
{desc: "filter map 3", expr: `filter [alpha:"hello" bravo:"world"] { |k v| eq $v "alpha" }`, want: map[string]any{}},
|
||||||
|
|
||||||
{desc: "filter itr 1", expr: `$s = "" ; itr | filter { |x| ne $x 2 } | for { |x| $s = "$s $x" }; $s`, want: " 1 3"},
|
{desc: "filter itr 1", expr: `s = "" ; itr | filter { |x| ne $x 2 } | for { |x| s = "$s $x" }; $s`, want: " 1 3"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -1408,10 +1408,10 @@ func TestBuiltins_Head(t *testing.T) {
|
||||||
{desc: "head list 1", expr: `head [1 2 3]`, want: 1},
|
{desc: "head list 1", expr: `head [1 2 3]`, want: 1},
|
||||||
|
|
||||||
{desc: "head itr 1", expr: `head (itr)`, want: 1},
|
{desc: "head itr 1", expr: `head (itr)`, want: 1},
|
||||||
{desc: "head itr 2", expr: `$h = (itr) ; head $h`, want: 1},
|
{desc: "head itr 2", expr: `h = (itr) ; head $h`, want: 1},
|
||||||
{desc: "head itr 3", expr: `$h = (itr) ; head $h ; head $h`, want: 2},
|
{desc: "head itr 3", expr: `h = (itr) ; head $h ; head $h`, want: 2},
|
||||||
{desc: "head itr 4", expr: `$h = (itr) ; head $h ; head $h ; head $h`, want: 3},
|
{desc: "head itr 4", expr: `h = (itr) ; head $h ; head $h ; head $h`, want: 3},
|
||||||
{desc: "head itr 5", expr: `$h = (itr) ; head $h ; head $h ; head $h ; head $h`, want: nil},
|
{desc: "head itr 5", expr: `h = (itr) ; head $h ; head $h ; head $h ; head $h`, want: nil},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -1747,7 +1747,7 @@ func TestBuiltins_Cat(t *testing.T) {
|
||||||
{desc: "cat 6", expr: `cat "array = " [1 3 2 4]`, want: "array = [1 3 2 4]"},
|
{desc: "cat 6", expr: `cat "array = " [1 3 2 4]`, want: "array = [1 3 2 4]"},
|
||||||
{desc: "cat 7", expr: `cat 1 $true 3 [4]`, want: "1true3[4]"},
|
{desc: "cat 7", expr: `cat 1 $true 3 [4]`, want: "1true3[4]"},
|
||||||
{desc: "cat 8", expr: `cat`, want: ""},
|
{desc: "cat 8", expr: `cat`, want: ""},
|
||||||
{desc: "cat 9", expr: `$x = ["a" "b" "c"] ; cat "array = " [1 $x.(0) $x.(2) $x.(1)]`, want: "array = [1 a c b]"},
|
{desc: "cat 9", expr: `x = ["a" "b" "c"] ; cat "array = " [1 $x.(0) $x.(2) $x.(1)]`, want: "array = [1 a c b]"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
|
@ -14,6 +14,8 @@ 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")
|
notIndexableError = newBadUsage("index only support on lists and hashes")
|
||||||
|
notModIndexableError = newBadUsage("list or hash cannot be modified")
|
||||||
|
assignToNilIndex = newBadUsage("assigning to nil index value")
|
||||||
)
|
)
|
||||||
|
|
||||||
type errorWithPos struct {
|
type errorWithPos struct {
|
||||||
|
|
49
ucl/eval.go
49
ucl/eval.go
|
@ -220,7 +220,51 @@ func (e evaluator) assignDot(ctx context.Context, ec *evalCtx, n astDot, toVal O
|
||||||
return e.assignArg(ctx, ec, n.Arg, toVal)
|
return e.assignArg(ctx, ec, n.Arg, toVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("TODO")
|
val, err := e.evalArgForDotAssign(ctx, ec, n.Arg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, dot := range n.DotSuffix {
|
||||||
|
isLast := i == len(n.DotSuffix)-1
|
||||||
|
|
||||||
|
var idx Object
|
||||||
|
if dot.KeyIdent != nil {
|
||||||
|
idx = StringObject(dot.KeyIdent.String())
|
||||||
|
} else {
|
||||||
|
idx, err = e.evalPipeline(ctx, ec, dot.Pipeline)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isLast {
|
||||||
|
val, err = indexAssign(ctx, val, idx, toVal, n.Pos)
|
||||||
|
} else {
|
||||||
|
val, err = indexLookup(ctx, val, idx, n.Pos)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e evaluator) evalArgForDotAssign(ctx context.Context, ec *evalCtx, n astCmdArg) (Object, error) {
|
||||||
|
// Special case for dot assigns of 'a.b = c' where a is actually a var deref (i.e. $a)
|
||||||
|
// which is unnecessary for assignments. Likewise, having '$a.b = c' should be dissallowed
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case n.Ident != nil:
|
||||||
|
if v, ok := ec.getVar(n.Ident.String()); ok {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
case n.Var != nil:
|
||||||
|
return nil, errors.New("cannot assign to a dereferenced variable")
|
||||||
|
}
|
||||||
|
return e.evalArg(ctx, ec, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -266,8 +310,7 @@ func (e evaluator) assignArg(ctx context.Context, ec *evalCtx, n astCmdArg, toVa
|
||||||
case n.Literal != nil:
|
case n.Literal != nil:
|
||||||
return nil, errors.New("cannot assign to a literal value")
|
return nil, errors.New("cannot assign to a literal value")
|
||||||
case n.Var != nil:
|
case n.Var != nil:
|
||||||
ec.setOrDefineVar(*n.Var, toVal)
|
return nil, errors.New("cannot assign to a dereferenced variable")
|
||||||
return toVal, nil
|
|
||||||
case n.PseudoVar != nil:
|
case n.PseudoVar != nil:
|
||||||
pvar, ok := ec.getPseudoVar(*n.PseudoVar)
|
pvar, ok := ec.getPseudoVar(*n.PseudoVar)
|
||||||
if ok {
|
if ok {
|
||||||
|
|
103
ucl/inst_test.go
103
ucl/inst_test.go
|
@ -27,15 +27,15 @@ func TestInst_Eval(t *testing.T) {
|
||||||
{desc: "simple ident 1", expr: `firstarg a-test`, want: "a-test"},
|
{desc: "simple ident 1", expr: `firstarg a-test`, want: "a-test"},
|
||||||
|
|
||||||
// String interpolation
|
// String interpolation
|
||||||
{desc: "interpolate string 1", expr: `$what = "world" ; firstarg "hello $what"`, want: "hello world"},
|
{desc: "interpolate string 1", expr: `what = "world" ; firstarg "hello $what"`, want: "hello world"},
|
||||||
{desc: "interpolate string 2", expr: `$what = "world" ; $when = "now" ; firstarg "$when, hello $what"`, want: "now, hello world"},
|
{desc: "interpolate string 2", expr: `what = "world" ; when = "now" ; firstarg "$when, hello $what"`, want: "now, hello world"},
|
||||||
{desc: "interpolate string 3", expr: `$what = "world" ; $when = "now" ; firstarg "${when}, hello ${what}"`, want: "now, hello world"},
|
{desc: "interpolate string 3", expr: `what = "world" ; when = "now" ; firstarg "${when}, hello ${what}"`, want: "now, hello world"},
|
||||||
{desc: "interpolate string 4", expr: `$crazy = [far: "unknown"] ; firstarg "hello ${crazy.far}"`, want: "hello unknown"},
|
{desc: "interpolate string 4", expr: `crazy = [far: "unknown"] ; firstarg "hello ${crazy.far}"`, want: "hello unknown"},
|
||||||
{desc: "interpolate string 5", expr: `$oldWords = ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(1)}"`, want: "hello thither"},
|
{desc: "interpolate string 5", expr: `oldWords = ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(1)}"`, want: "hello thither"},
|
||||||
{desc: "interpolate string 6", expr: `$oldWords = ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(add 1 1)}"`, want: "hello yonder"},
|
{desc: "interpolate string 6", expr: `oldWords = ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(add 1 1)}"`, want: "hello yonder"},
|
||||||
{desc: "interpolate string 7", expr: `$oldWords = ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(add 2 | sub (sub 2 1) | sub 1)}"`, want: "hello hither"},
|
{desc: "interpolate string 7", expr: `oldWords = ["hither" "thither" "yonder"] ; firstarg "hello ${oldWords.(add 2 | sub (sub 2 1) | sub 1)}"`, want: "hello hither"},
|
||||||
{desc: "interpolate string 8", expr: `$words = ["old": ["hither" "thither" "yonder"] "new": ["near" "far"]] ; firstarg "hello ${words.old.(2)}"`, want: "hello yonder"},
|
{desc: "interpolate string 8", expr: `words = ["old": ["hither" "thither" "yonder"] "new": ["near" "far"]] ; firstarg "hello ${words.old.(2)}"`, want: "hello yonder"},
|
||||||
{desc: "interpolate string 9", expr: `$what = "world" ; firstarg "hello $($what)"`, want: "hello world"},
|
{desc: "interpolate string 9", expr: `what = "world" ; firstarg "hello $($what)"`, want: "hello world"},
|
||||||
{desc: "interpolate string 10", expr: `firstarg "hello $([1 2 3] | len)"`, want: "hello 3"},
|
{desc: "interpolate string 10", expr: `firstarg "hello $([1 2 3] | len)"`, want: "hello 3"},
|
||||||
{desc: "interpolate string 11", expr: `firstarg "hello $(add (add 1 2) 3)"`, want: "hello 6"},
|
{desc: "interpolate string 11", expr: `firstarg "hello $(add (add 1 2) 3)"`, want: "hello 6"},
|
||||||
{desc: "interpolate string 12", expr: `firstarg ("$(add 2 (add 1 1)) + $([1 2 3].(1) | cat ("$("")")) = $(("$(add 2 (4))"))")`, want: "4 + 2 = 6"},
|
{desc: "interpolate string 12", expr: `firstarg ("$(add 2 (add 1 1)) + $([1 2 3].(1) | cat ("$("")")) = $(("$(add 2 (4))"))")`, want: "4 + 2 = 6"},
|
||||||
|
@ -62,60 +62,71 @@ func TestInst_Eval(t *testing.T) {
|
||||||
// Multi-statements
|
// Multi-statements
|
||||||
{desc: "multi 1", expr: `firstarg "hello" ; firstarg "world"`, want: "world"},
|
{desc: "multi 1", expr: `firstarg "hello" ; firstarg "world"`, want: "world"},
|
||||||
{desc: "multi 2", expr: `list "hello" | toUpper ; firstarg "world"`, want: "world"},
|
{desc: "multi 2", expr: `list "hello" | toUpper ; firstarg "world"`, want: "world"},
|
||||||
{desc: "multi 3", expr: `$new = "this is new" ; firstarg $new`, want: "this is new"},
|
{desc: "multi 3", expr: `new = "this is new" ; firstarg $new`, want: "this is new"},
|
||||||
|
|
||||||
// Lists
|
// Lists
|
||||||
{desc: "list 1", expr: `firstarg ["1" "2" "3"]`, want: []any{"1", "2", "3"}},
|
{desc: "list 1", expr: `firstarg ["1" "2" "3"]`, want: []any{"1", "2", "3"}},
|
||||||
{desc: "list 2", expr: `$one = "one" ; firstarg [$one (list "two" | map { |x| toUpper $x } | head) "three"]`, want: []any{"one", "TWO", "three"}},
|
{desc: "list 2", expr: `one = "one" ; firstarg [$one (list "two" | map { |x| toUpper $x } | head) "three"]`, want: []any{"one", "TWO", "three"}},
|
||||||
{desc: "list 3", expr: `firstarg []`, want: []any{}},
|
{desc: "list 3", expr: `firstarg []`, want: []any{}},
|
||||||
{desc: "list 4", expr: `$x = ["a" "b" "c"] ; firstarg [$x.(2) $x.(1) $x.(0)]`, want: []any{"c", "b", "a"}},
|
{desc: "list 4", expr: `x = ["a" "b" "c"] ; firstarg [$x.(2) $x.(1) $x.(0)]`, want: []any{"c", "b", "a"}},
|
||||||
|
|
||||||
// Maps
|
// Maps
|
||||||
{desc: "map 1", expr: `firstarg [one:"1" two:"2" three:"3"]`, want: map[string]any{"one": "1", "two": "2", "three": "3"}},
|
{desc: "map 1", expr: `firstarg [one:"1" two:"2" three:"3"]`, want: map[string]any{"one": "1", "two": "2", "three": "3"}},
|
||||||
{desc: "map 2", expr: `firstarg ["one":"1" "two":"2" "three":"3"]`, want: map[string]any{"one": "1", "two": "2", "three": "3"}},
|
{desc: "map 2", expr: `firstarg ["one":"1" "two":"2" "three":"3"]`, want: map[string]any{"one": "1", "two": "2", "three": "3"}},
|
||||||
{desc: "map 3", expr: `
|
{desc: "map 3", expr: `
|
||||||
$one = "one" ; $n1 = "1"
|
one = "one" ; n1 = "1"
|
||||||
firstarg [
|
firstarg [
|
||||||
$one:$n1
|
$one:$n1
|
||||||
(list "two" | map { |x| toUpper $x } | head):(list "2" | map { |x| toUpper $x } | head)
|
(list "two" | map { |x| toUpper $x } | head):(list "2" | map { |x| toUpper $x } | head)
|
||||||
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{}},
|
||||||
{desc: "map 5", expr: `$x = ["a" "b" "c"] ; firstarg ["one":$x.(2) "two":$x.(1) "three":$x.(0)]`, want: map[string]any{"one": "c", "two": "b", "three": "a"}},
|
{desc: "map 5", expr: `x = ["a" "b" "c"] ; firstarg ["one":$x.(2) "two":$x.(1) "three":$x.(0)]`, want: map[string]any{"one": "c", "two": "b", "three": "a"}},
|
||||||
{desc: "map 6", expr: `$x = [a:"A" b:"B" c:"C"] ; firstarg ["one":$x.c "two":$x.b "three":$x.a]`, want: map[string]any{"one": "C", "two": "B", "three": "A"}},
|
{desc: "map 6", expr: `x = [a:"A" b:"B" c:"C"] ; firstarg ["one":$x.c "two":$x.b "three":$x.a]`, want: map[string]any{"one": "C", "two": "B", "three": "A"}},
|
||||||
|
|
||||||
// Dots
|
// Dots
|
||||||
{desc: "dot expr 1", expr: `$x = [1 2 3] ; $x.(0)`, want: 1},
|
{desc: "dot expr 1", expr: `x = [1 2 3] ; $x.(0)`, want: 1},
|
||||||
{desc: "dot expr 2", expr: `$x = [1 2 3] ; $x.(1)`, want: 2},
|
{desc: "dot expr 2", expr: `x = [1 2 3] ; $x.(1)`, want: 2},
|
||||||
{desc: "dot expr 3", expr: `$x = [1 2 3] ; $x.(2)`, want: 3},
|
{desc: "dot expr 3", expr: `x = [1 2 3] ; $x.(2)`, want: 3},
|
||||||
{desc: "dot expr 4", expr: `$x = [1 2 3] ; $x.(3)`, want: nil},
|
{desc: "dot expr 4", expr: `x = [1 2 3] ; $x.(3)`, want: nil},
|
||||||
{desc: "dot expr 5", expr: `$x = [1 2 3] ; $x.(add 1 1)`, want: 3},
|
{desc: "dot expr 5", expr: `x = [1 2 3] ; $x.(add 1 1)`, want: 3},
|
||||||
{desc: "dot expr 6", expr: `$x = [1 2 3] ; $x.(-1)`, want: 3},
|
{desc: "dot expr 6", expr: `x = [1 2 3] ; $x.(-1)`, want: 3},
|
||||||
{desc: "dot expr 7", expr: `$x = [1 2 3] ; $x.(-2)`, want: 2},
|
{desc: "dot expr 7", expr: `x = [1 2 3] ; $x.(-2)`, want: 2},
|
||||||
{desc: "dot expr 8", expr: `$x = [1 2 3] ; $x.(-3)`, want: 1},
|
{desc: "dot expr 8", expr: `x = [1 2 3] ; $x.(-3)`, want: 1},
|
||||||
{desc: "dot expr 9", expr: `$x = [1 2 3] ; $x.(-4)`, want: nil},
|
{desc: "dot expr 9", expr: `x = [1 2 3] ; $x.(-4)`, want: nil},
|
||||||
|
|
||||||
{desc: "dot idents 1", expr: `$x = [alpha:"hello" bravo:"world"] ; $x.alpha`, want: "hello"},
|
{desc: "dot idents 1", expr: `x = [alpha:"hello" bravo:"world"] ; $x.alpha`, want: "hello"},
|
||||||
{desc: "dot idents 2", expr: `$x = [alpha:"hello" bravo:"world"] ; $x.bravo`, want: "world"},
|
{desc: "dot idents 2", expr: `x = [alpha:"hello" bravo:"world"] ; $x.bravo`, want: "world"},
|
||||||
{desc: "dot idents 3", expr: `$x = [alpha:"hello" bravo:"world"] ; $x.charlie`, want: nil},
|
{desc: "dot idents 3", expr: `x = [alpha:"hello" bravo:"world"] ; $x.charlie`, want: nil},
|
||||||
{desc: "dot idents 4", expr: `$x = [alpha:"hello" bravo:"world"] ; $x.("alpha")`, want: "hello"},
|
{desc: "dot idents 4", expr: `x = [alpha:"hello" bravo:"world"] ; $x.("alpha")`, want: "hello"},
|
||||||
{desc: "dot idents 5", expr: `$x = [alpha:"hello" bravo:"world"] ; $x.("bravo")`, want: "world"},
|
{desc: "dot idents 5", expr: `x = [alpha:"hello" bravo:"world"] ; $x.("bravo")`, want: "world"},
|
||||||
{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 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 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 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 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 5", expr: `x = 123 ; $x.(5)`, wantAnErr: true},
|
||||||
{desc: "dot err 6", expr: `$x = 123 ; $x.Five`, 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},
|
||||||
{desc: "parse comments 3", expr: parseComments3, wantObj: true, wantErr: nil},
|
{desc: "parse comments 3", expr: parseComments3, wantObj: true, wantErr: nil},
|
||||||
{desc: "parse comments 4", expr: parseComments4, wantObj: true, wantErr: nil},
|
{desc: "parse comments 4", expr: parseComments4, wantObj: true, wantErr: nil},
|
||||||
|
|
||||||
|
// Assign dots
|
||||||
|
{desc: "assign dot 1", expr: `x = [1 2 3] ; x.(0) = 4 ; "$x"`, want: "[4 2 3]"},
|
||||||
|
{desc: "assign dot 2", expr: `x = [1 2 3] ; x.(1) = 5 ; "$x"`, want: "[1 5 3]"},
|
||||||
|
{desc: "assign dot 3", expr: `x = [1 2 3] ; x.(-1) = 6 ; "$x"`, want: "[1 2 6]"},
|
||||||
|
{desc: "assign dot 4", expr: `y = [a:1 b:2] ; y.a = "hello" ; "$y"`, want: `[a:hello b:2]`},
|
||||||
|
{desc: "assign dot 5", expr: `y = [a:1 b:2] ; y.b = "world" ; "$y"`, want: `[a:1 b:world]`},
|
||||||
|
{desc: "assign dot 6", expr: `y = [a:"b" b:2] ; y.($y.a) = "world" ; "$y"`, want: `[a:b b:world]`},
|
||||||
|
{desc: "assign dot 7", expr: `z = [a:[1 2] b:[3 3]] ; z.a.(1) = 3 ; "$z"`, want: `[a:[1 3] b:[3 3]]`},
|
||||||
|
{desc: "assign dot 8", expr: `z = [[1 2] [3 4]] ; z.(1).(0) = 5 ; "$z"`, want: `[[1 2] [5 4]]`},
|
||||||
|
{desc: "assign dot 7", expr: `z = [[a:1 b:2] [c:3 d:4]] ; z.(1).a = 5 ; "$z"`, want: `[[a:1 b:2] [a:5 c:3 d:4]]`},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -148,7 +159,7 @@ func TestInst_Eval_WithSubEnv(t *testing.T) {
|
||||||
|
|
||||||
inst := ucl.New()
|
inst := ucl.New()
|
||||||
|
|
||||||
res, err := inst.Eval(ctx, strings.NewReader(`$a = "hello" ; $a`), ucl.WithSubEnv())
|
res, err := inst.Eval(ctx, strings.NewReader(`a = "hello" ; $a`), ucl.WithSubEnv())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "hello", res)
|
assert.Equal(t, "hello", res)
|
||||||
|
|
||||||
|
@ -167,15 +178,15 @@ func TestInst_Eval_WithSubEnv(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
descr: "reading vars",
|
descr: "reading vars",
|
||||||
eval1: `$a = "hello" ; hook { $a }`,
|
eval1: `a = "hello" ; hook { $a }`,
|
||||||
eval2: `$a = "world" ; hook { $a }`,
|
eval2: `a = "world" ; hook { $a }`,
|
||||||
want1: "hello",
|
want1: "hello",
|
||||||
want2: "world",
|
want2: "world",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
descr: "modifying vars",
|
descr: "modifying vars",
|
||||||
eval1: `$a = "hello" ; hook { $a = "new value" ; $a }`,
|
eval1: `a = "hello" ; hook { a = "new value" ; $a }`,
|
||||||
eval2: `$a = "world" ; hook { $a }`,
|
eval2: `a = "world" ; hook { $a }`,
|
||||||
want1: "new value",
|
want1: "new value",
|
||||||
want2: "world",
|
want2: "world",
|
||||||
},
|
},
|
||||||
|
@ -195,8 +206,8 @@ func TestInst_Eval_WithSubEnv(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
descr: "exporting procs 2",
|
descr: "exporting procs 2",
|
||||||
eval1: `$a = "hello" ; export say_hello { $a = "world"; $a } ; hook { say_hello }`,
|
eval1: `a = "hello" ; export say_hello { a = "world"; $a } ; hook { say_hello }`,
|
||||||
eval2: `$a = "other" ; hook { say_hello }`,
|
eval2: `a = "other" ; hook { say_hello }`,
|
||||||
want1: "world",
|
want1: "world",
|
||||||
want2: "world",
|
want2: "world",
|
||||||
},
|
},
|
||||||
|
|
32
ucl/objs.go
32
ucl/objs.go
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -35,9 +36,13 @@ type ModListable interface {
|
||||||
|
|
||||||
// Insert adds a new item to the list. idx can be a positive
|
// Insert adds a new item to the list. idx can be a positive
|
||||||
// number from 0 to len(), in which case the object will be inserted
|
// number from 0 to len(), in which case the object will be inserted
|
||||||
// at that position. If idx is negative, then the item will be inserted
|
// at that position, shifting all other elements to the right.
|
||||||
|
// If idx is negative, then the item will be inserted
|
||||||
// at that position from the right.
|
// at that position from the right.
|
||||||
Insert(idx int, obj Object) error
|
Insert(idx int, obj Object) error
|
||||||
|
|
||||||
|
// SetIndex replaces the item at index position idx with obj.
|
||||||
|
SetIndex(idx int, obj Object) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Hashable interface {
|
type Hashable interface {
|
||||||
|
@ -46,6 +51,11 @@ type Hashable interface {
|
||||||
Each(func(k string, v Object) error) error
|
Each(func(k string, v Object) error) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ModHashable interface {
|
||||||
|
Hashable
|
||||||
|
SetValue(k string, val Object) error
|
||||||
|
}
|
||||||
|
|
||||||
type ListObject []Object
|
type ListObject []Object
|
||||||
|
|
||||||
func NewListObject() *ListObject {
|
func NewListObject() *ListObject {
|
||||||
|
@ -80,6 +90,11 @@ func (s *ListObject) Index(i int) Object {
|
||||||
return (*s)[i]
|
return (*s)[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ListObject) SetIndex(i int, toVal Object) error {
|
||||||
|
(*s)[i] = toVal
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type StringListObject []string
|
type StringListObject []string
|
||||||
|
|
||||||
func (ss StringListObject) String() string {
|
func (ss StringListObject) String() string {
|
||||||
|
@ -117,9 +132,17 @@ func (s HashObject) String() string {
|
||||||
return "[:]"
|
return "[:]"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the keys in sorted order
|
||||||
|
keys := make([]string, 0, len(s))
|
||||||
|
for k := range s {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
sb := strings.Builder{}
|
sb := strings.Builder{}
|
||||||
sb.WriteString("[")
|
sb.WriteString("[")
|
||||||
for k, v := range s {
|
for _, k := range keys {
|
||||||
|
v := s[k]
|
||||||
if sb.Len() != 1 {
|
if sb.Len() != 1 {
|
||||||
sb.WriteString(" ")
|
sb.WriteString(" ")
|
||||||
}
|
}
|
||||||
|
@ -152,6 +175,11 @@ func (s HashObject) Each(fn func(k string, v Object) error) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s HashObject) SetValue(k string, val Object) error {
|
||||||
|
s[k] = val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type StringObject string
|
type StringObject string
|
||||||
|
|
||||||
func (s StringObject) String() string {
|
func (s StringObject) String() string {
|
||||||
|
|
|
@ -124,7 +124,7 @@ func TestInst_SetBuiltin(t *testing.T) {
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{descr: "pass via args", expr: `join (add2 "left" "right")`, want: "left:right"},
|
{descr: "pass via args", expr: `join (add2 "left" "right")`, want: "left:right"},
|
||||||
{descr: "pass via vars", expr: `$x = (add2 "blue" "green") ; join $x`, want: "blue:green"},
|
{descr: "pass via vars", expr: `x = (add2 "blue" "green") ; join $x`, want: "blue:green"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
Loading…
Reference in a new issue