the assign operators, '=' and ':=', can set the value of dict items

This commit is contained in:
2026-05-17 07:15:12 +02:00
parent 3ccbeb3978
commit 1bf8015da1
4 changed files with 114 additions and 21 deletions
+3 -1
View File
@@ -10,6 +10,8 @@ import (
"strings"
)
const DictTypeName = "dict"
type DictType map[any]any
func IsDict(v any) (ok bool) {
@@ -131,7 +133,7 @@ func (dict *DictType) String() string {
}
func (dict *DictType) TypeName() string {
return "dict"
return DictTypeName
}
func (dict *DictType) HasKey(target any) (ok bool) {
+37 -1
View File
@@ -89,6 +89,40 @@ func assignValue(ctx kern.ExprContext, leftTerm *scan.Term, v any, deepCopy bool
return
}
func evalAssignDictItem(ctx kern.ExprContext, dotTerm *scan.Term, valueTerm kern.Term) (v any, err error) {
var ok bool
var dictAny, dotKey any
var dict *kern.DictType
if err = dotTerm.CheckOperands(); err != nil {
return
}
dotLeftTerm := dotTerm.GetChild(0)
if dictAny, err = dotLeftTerm.Compute(ctx); err != nil {
return
}
if dict, ok = dictAny.(*kern.DictType); !ok {
err = dotTerm.Tk.ErrorExpectedGot(kern.DictTypeName)
return
}
dotRightTerm := dotTerm.Children[1]
dotRightSym := dotRightTerm.Symbol()
if dotRightSym == scan.SymVariable || dotRightSym == scan.SymString {
dotKey = util.UnquoteString(dotRightTerm.Source())
} else if dotKey, err = dotRightTerm.Compute(ctx); err != nil {
return
}
if v, err = valueTerm.Compute(ctx); err != nil {
return
}
dict.SetItem(dotKey, v)
return
}
func generalEvalAssign(ctx kern.ExprContext, opTerm *scan.Term, deepCopy bool) (v any, err error) {
if err = opTerm.CheckOperands(); err != nil {
return
@@ -96,7 +130,9 @@ func generalEvalAssign(ctx kern.ExprContext, opTerm *scan.Term, deepCopy bool) (
leftTerm := opTerm.Children[0]
leftSym := leftTerm.Symbol()
if leftSym != scan.SymVariable && leftSym != scan.SymIndex {
if leftSym == scan.SymDot {
return evalAssignDictItem(ctx, opTerm.Children[0], opTerm.GetChild(1))
} else if leftSym != scan.SymVariable && leftSym != scan.SymIndex {
err = leftTerm.Tk.Errorf("left operand of %q must be a variable or a collection's item", opTerm.Tk.Source())
return
}
+46 -16
View File
@@ -7,6 +7,7 @@ package expr
import (
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/util"
)
// -------- dot term
@@ -34,7 +35,7 @@ func evalDot(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
switch unboxedValue := leftValue.(type) {
case kern.ExtIterator:
if indexTerm.Tk.Sym == scan.SymVariable /*|| indexTerm.Tk.Sym == scan.SymString */ {
if indexTerm.Symbol() == scan.SymVariable /*|| indexTerm.Tk.Sym == scan.SymString */ {
opName := indexTerm.Source()
if unboxedValue.HasOperation(opName) {
v, err = unboxedValue.CallOperation(opName, map[string]any{})
@@ -46,21 +47,22 @@ func evalDot(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
err = indexTerm.Tk.ErrorExpectedGot("identifier")
}
case *kern.DictType:
var ok bool
s := opTerm.Children[1].Tk.Sym
if s == scan.SymVariable || s == scan.SymString {
src := opTerm.Children[1].Source()
if len(src) > 1 && src[0] == '"' && src[len(src)-1] == '"' {
src = src[1 : len(src)-1]
}
if v, ok = unboxedValue.GetItem(src); !ok {
err = opTerm.Errorf("key %q not found", src)
}
} else if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
if v, ok = unboxedValue.GetItem(rightValue); !ok {
err = opTerm.Errorf("key %q not found", rightValue)
}
}
// var ok bool
// s := opTerm.Children[1].Symbol()
// if s == scan.SymVariable || s == scan.SymString {
// src := opTerm.Children[1].Source()
// if len(src) > 1 && src[0] == '"' && src[len(src)-1] == '"' {
// src = src[1 : len(src)-1]
// }
// if v, ok = unboxedValue.GetItem(src); !ok {
// err = opTerm.Errorf("key %q not found", src)
// }
// } else if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
// if v, ok = unboxedValue.GetItem(rightValue); !ok {
// err = opTerm.Errorf("key %q not found", rightValue)
// }
// }
v, err = dotGetDictItemValue(ctx, unboxedValue, opTerm.Children[1])
default:
if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
err = opTerm.Errorf("incompatible types: %s and %s", kern.TypeName(leftValue), kern.TypeName(rightValue))
@@ -69,6 +71,34 @@ func evalDot(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
return
}
func dotGetDictItemValue(ctx kern.ExprContext, d *kern.DictType, rightTerm *scan.Term) (v any, err error) {
var ok bool
var rightValue any
s := rightTerm.Symbol()
if s == scan.SymVariable || s == scan.SymString {
// src := rightTerm.Source()
// if len(src) > 1 && src[0] == '"' && src[len(src)-1] == '"' {
// src = src[1 : len(src)-1]
// }
src := util.UnquoteString(rightTerm.Source())
if v, ok = d.GetItem(src); !ok {
err = errDictKeyNotFound(rightTerm, src)
}
} else if rightValue, err = rightTerm.Compute(ctx); err == nil {
if v, ok = d.GetItem(rightValue); !ok {
err = errDictKeyNotFound(rightTerm, rightValue)
}
}
return
}
func errDictKeyNotFound(term *scan.Term, key any) error {
if s, ok := key.(string); ok {
return term.Errorf("key %q not found", s)
}
return term.Errorf("key %v not found", key)
}
// init
func init() {
scan.RegisterTermConstructor(scan.SymDot, newDotTerm)
+28 -3
View File
@@ -36,11 +36,36 @@ func TestDictParser(t *testing.T) {
/* 13 */ {`D={"a":1, "b":2}; D."a"`, int64(1), nil},
/* 14 */ {`D={"a":1, "b":2}; D.a`, int64(1), nil},
/* 15 */ {`D={1:"a", 2:"b", 3:"c"}; D.(1+2)`, "c", nil},
/* 16 */ {`D={"a":1, "b":2}; D.a`, int64(1), nil},
/* 16 */ {`D={1:"a"}; D.2`, nil, `[1:15] key 2 not found`},
/* 17 */ {`D={1:"a"}; D."2"`, nil, `[1:17] key "2" not found`},
/* 18 */ {`D={"a":1, "b":2}; D.a = 10; D.a`, int64(10), nil},
// /* 19 */ {`D={"a": {"one": 1}}; D.a."one" = 10; D["a"]["one"]`, int64(10), nil},
}
runTestSuiteSpec(t, section, inputs, 16)
// runTestSuite(t, section, inputs)
// runTestSuiteSpec(t, section, inputs, 16)
runTestSuite(t, section, inputs)
}
func _TestPoc(t *testing.T) {
section := "Dict-Assign-Item"
ctx := NewSimpleStore()
ctx.UnsafeSetVar(
"D",
kern.NewDict(map[any]any{
"a": kern.NewDict(map[any]any{
"uno": int64(10),
},
),
}),
) // D={"a": {"uno":1}}
inputs := []inputType{
/* 1 */ {`D.a.uno`, int64(10), nil},
/* 2 */ {`D.a.uno = 111; D.a."uno"`, int64(111), nil},
/* 3 */ {`D["a"]["uno"]`, int64(111), nil},
}
runCtxTestSuiteSpec(t, ctx, section, inputs, 3)
// runCtxTestSuite(t, ctx, section, inputs)
}
func TestDictToStringMultiLine(t *testing.T) {