From 1bf8015da1b186ca7360346627bb8ec7fe1dbc9d Mon Sep 17 00:00:00 2001 From: Celestino Amoroso Date: Sun, 17 May 2026 07:15:12 +0200 Subject: [PATCH] the assign operators, '=' and ':=', can set the value of dict items --- kern/dict-type.go | 4 ++- operator-assign.go | 38 +++++++++++++++++++++++++++- operator-dot.go | 62 ++++++++++++++++++++++++++++++++++------------ t_dict_test.go | 31 ++++++++++++++++++++--- 4 files changed, 114 insertions(+), 21 deletions(-) diff --git a/kern/dict-type.go b/kern/dict-type.go index 342fc24..dd2bb5f 100644 --- a/kern/dict-type.go +++ b/kern/dict-type.go @@ -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) { diff --git a/operator-assign.go b/operator-assign.go index 0fa1a8b..e7f306a 100644 --- a/operator-assign.go +++ b/operator-assign.go @@ -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 } diff --git a/operator-dot.go b/operator-dot.go index 006ef92..353567e 100644 --- a/operator-dot.go +++ b/operator-dot.go @@ -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) diff --git a/t_dict_test.go b/t_dict_test.go index 62d2f56..a064b0a 100644 --- a/t_dict_test.go +++ b/t_dict_test.go @@ -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) {