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" "strings"
) )
const DictTypeName = "dict"
type DictType map[any]any type DictType map[any]any
func IsDict(v any) (ok bool) { func IsDict(v any) (ok bool) {
@@ -131,7 +133,7 @@ func (dict *DictType) String() string {
} }
func (dict *DictType) TypeName() string { func (dict *DictType) TypeName() string {
return "dict" return DictTypeName
} }
func (dict *DictType) HasKey(target any) (ok bool) { 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 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) { func generalEvalAssign(ctx kern.ExprContext, opTerm *scan.Term, deepCopy bool) (v any, err error) {
if err = opTerm.CheckOperands(); err != nil { if err = opTerm.CheckOperands(); err != nil {
return return
@@ -96,7 +130,9 @@ func generalEvalAssign(ctx kern.ExprContext, opTerm *scan.Term, deepCopy bool) (
leftTerm := opTerm.Children[0] leftTerm := opTerm.Children[0]
leftSym := leftTerm.Symbol() 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()) err = leftTerm.Tk.Errorf("left operand of %q must be a variable or a collection's item", opTerm.Tk.Source())
return return
} }
+46 -16
View File
@@ -7,6 +7,7 @@ package expr
import ( import (
"git.portale-stac.it/go-pkg/expr/kern" "git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan" "git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/expr/util"
) )
// -------- dot term // -------- dot term
@@ -34,7 +35,7 @@ func evalDot(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
switch unboxedValue := leftValue.(type) { switch unboxedValue := leftValue.(type) {
case kern.ExtIterator: 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() opName := indexTerm.Source()
if unboxedValue.HasOperation(opName) { if unboxedValue.HasOperation(opName) {
v, err = unboxedValue.CallOperation(opName, map[string]any{}) 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") err = indexTerm.Tk.ErrorExpectedGot("identifier")
} }
case *kern.DictType: case *kern.DictType:
var ok bool // var ok bool
s := opTerm.Children[1].Tk.Sym // s := opTerm.Children[1].Symbol()
if s == scan.SymVariable || s == scan.SymString { // if s == scan.SymVariable || s == scan.SymString {
src := opTerm.Children[1].Source() // src := opTerm.Children[1].Source()
if len(src) > 1 && src[0] == '"' && src[len(src)-1] == '"' { // if len(src) > 1 && src[0] == '"' && src[len(src)-1] == '"' {
src = src[1 : len(src)-1] // src = src[1 : len(src)-1]
} // }
if v, ok = unboxedValue.GetItem(src); !ok { // if v, ok = unboxedValue.GetItem(src); !ok {
err = opTerm.Errorf("key %q not found", src) // err = opTerm.Errorf("key %q not found", src)
} // }
} else if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil { // } else if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
if v, ok = unboxedValue.GetItem(rightValue); !ok { // if v, ok = unboxedValue.GetItem(rightValue); !ok {
err = opTerm.Errorf("key %q not found", rightValue) // err = opTerm.Errorf("key %q not found", rightValue)
} // }
} // }
v, err = dotGetDictItemValue(ctx, unboxedValue, opTerm.Children[1])
default: default:
if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil { if rightValue, err = opTerm.Children[1].Compute(ctx); err == nil {
err = opTerm.Errorf("incompatible types: %s and %s", kern.TypeName(leftValue), kern.TypeName(rightValue)) 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 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 // init
func init() { func init() {
scan.RegisterTermConstructor(scan.SymDot, newDotTerm) 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}, /* 13 */ {`D={"a":1, "b":2}; D."a"`, int64(1), nil},
/* 14 */ {`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}, /* 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) // runTestSuiteSpec(t, section, inputs, 16)
// runTestSuite(t, section, inputs) 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) { func TestDictToStringMultiLine(t *testing.T) {