This commit is contained in:
parent
2172fb86d8
commit
e2f471c608
|
@ -539,6 +539,39 @@ func indexLookup(ctx context.Context, obj, elem Object, pos lexer.Position) (Obj
|
|||
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) {
|
||||
if err := args.expectArgn(1); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -14,6 +14,8 @@ var (
|
|||
var (
|
||||
tooManyFinallyBlocksError = newBadUsage("try needs at most 1 finally")
|
||||
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 {
|
||||
|
|
46
ucl/eval.go
46
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 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) {
|
||||
|
|
|
@ -116,6 +116,17 @@ func TestInst_Eval(t *testing.T) {
|
|||
{desc: "parse comments 2", expr: parseComments2, wantObj: true, wantErr: nil},
|
||||
{desc: "parse comments 3", expr: parseComments3, 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 {
|
||||
|
|
32
ucl/objs.go
32
ucl/objs.go
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -35,9 +36,13 @@ type ModListable interface {
|
|||
|
||||
// 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
|
||||
// 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.
|
||||
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 {
|
||||
|
@ -46,6 +51,11 @@ type Hashable interface {
|
|||
Each(func(k string, v Object) error) error
|
||||
}
|
||||
|
||||
type ModHashable interface {
|
||||
Hashable
|
||||
SetValue(k string, val Object) error
|
||||
}
|
||||
|
||||
type ListObject []Object
|
||||
|
||||
func NewListObject() *ListObject {
|
||||
|
@ -80,6 +90,11 @@ func (s *ListObject) Index(i int) Object {
|
|||
return (*s)[i]
|
||||
}
|
||||
|
||||
func (s *ListObject) SetIndex(i int, toVal Object) error {
|
||||
(*s)[i] = toVal
|
||||
return nil
|
||||
}
|
||||
|
||||
type StringListObject []string
|
||||
|
||||
func (ss StringListObject) String() string {
|
||||
|
@ -117,9 +132,17 @@ func (s HashObject) String() string {
|
|||
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.WriteString("[")
|
||||
for k, v := range s {
|
||||
for _, k := range keys {
|
||||
v := s[k]
|
||||
if sb.Len() != 1 {
|
||||
sb.WriteString(" ")
|
||||
}
|
||||
|
@ -152,6 +175,11 @@ func (s HashObject) Each(fn func(k string, v Object) error) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s HashObject) SetValue(k string, val Object) error {
|
||||
s[k] = val
|
||||
return nil
|
||||
}
|
||||
|
||||
type StringObject string
|
||||
|
||||
func (s StringObject) String() string {
|
||||
|
|
Loading…
Reference in a new issue