Compare commits

...

15 Commits

Author SHA1 Message Date
c0c2ab8b4e func-bolerplate.bash: generate Go source for a new functions module 2024-05-01 07:13:52 +02:00
288e93d708 func-string.go: string utils module 2024-05-01 07:10:59 +02:00
92e862da19 common definitions collections 2024-05-01 07:10:11 +02:00
dc6975e56c term.go: better error message in term.toInt() 2024-05-01 07:09:18 +02:00
6a2d3c53fd func-common.go common-errors.go 2024-05-01 07:09:01 +02:00
5643a57bcc utils.go: new function toInt() converts any(int64) to int 2024-05-01 07:07:36 +02:00
52fb398cd8 data-cursor.go: deep changes to better handle the data-source 2024-05-01 05:59:54 +02:00
cdbe3dfc22 commented tracing code 2024-05-01 05:57:08 +02:00
7aabd068ed func-math.go: add() now supports general iterators. TODO: do the same thing to mul() 2024-05-01 05:55:37 +02:00
924f5da725 Clone functions now filter ref variables to avoid to pass them to child context 2024-05-01 05:53:56 +02:00
d657cbb51e func-os.go: fixed the handling of err==io.EOF 2024-05-01 05:48:37 +02:00
be874503ec iterator.go: new error function errInvalidDataSource() 2024-05-01 05:46:24 +02:00
056d42d328 utils.go: Copy and Clone maps with filter function. Also added isIterator(). 2024-05-01 05:45:10 +02:00
aa66d07caa Iterators now support generic operation interface based on methods HasOperation() and CallOperation().
Also fixed mul() function that called doAdd() instead of doMul().
2024-04-28 06:45:39 +02:00
fc0e1ffaee New function isFunctor() 2024-04-28 06:43:57 +02:00
18 changed files with 523 additions and 59 deletions

37
common-errors.go Normal file
View File

@ -0,0 +1,37 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// common-errors.go
package expr
import (
"fmt"
)
// --- General errors
func errCantConvert(funcName string, value any, kind string) error {
return fmt.Errorf("%s() can't convert %T to %s", funcName, value, kind)
}
func errExpectedGot(funcName string, kind string, value any) error {
return fmt.Errorf("%s() expected %s, got %T (%v)", funcName, kind, value, value)
}
// --- Parameter errors
func errOneParam(funcName string) error {
return fmt.Errorf("%s() requires exactly one param", funcName)
}
func errMissingRequiredParameter(funcName, paramName string) error {
return fmt.Errorf("%s() missing required parameter %q", funcName, paramName)
}
func errInvalidParameterValue(funcName, paramName string, paramValue any) error {
return fmt.Errorf("%s() invalid value %T (%v) for parameter %q", funcName, paramValue, paramValue, paramName)
}
func errWrongParamType(funcName, paramName, paramType string, paramValue any) error {
return fmt.Errorf("%s() the %q parameter must be a %s, got a %T (%v)", funcName, paramName, paramType, paramValue, paramValue)
}

11
common-params.go Normal file
View File

@ -0,0 +1,11 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// common-params.go
package expr
const (
paramParts = "parts"
paramSeparator = "separator"
paramSource = "source"
)

13
common-type-names.go Normal file
View File

@ -0,0 +1,13 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// common-type-names.go
package expr
const (
typeBoolean = "boolean"
typeFloat = "decimal"
typeInt = "integer"
typeNumber = "number"
typeString = "string"
)

View File

@ -27,8 +27,10 @@ func exportFunc(ctx ExprContext, name string, info ExprFunc) {
func exportObjects(destCtx, sourceCtx ExprContext) {
exportAll := isEnabled(sourceCtx, control_export_all)
// fmt.Printf("Exporting from sourceCtx [%p] to destCtx [%p] -- exportAll=%t\n", sourceCtx, destCtx, exportAll)
// Export variables
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
// fmt.Printf("\tExporting %q\n", refName)
refValue, _ := sourceCtx.GetVar(refName)
exportVar(destCtx, refName, refValue)
}

View File

@ -6,7 +6,9 @@ package expr
import (
"errors"
"fmt"
"io"
"strings"
)
const (
@ -18,7 +20,7 @@ const (
)
type dataCursor struct {
ds map[any]*term
ds map[string]Functor
ctx ExprContext
index int
resource any
@ -28,36 +30,93 @@ type dataCursor struct {
currentFunc Functor
}
func newDataCursor(ctx ExprContext) (dc *dataCursor) {
func newDataCursor(ctx ExprContext, ds map[string]Functor) (dc *dataCursor) {
dc = &dataCursor{
ds: ds,
index: -1,
ctx: ctx.Clone(),
}
return
}
func mapToString(m map[string]Functor) string {
var sb strings.Builder
sb.WriteByte('{')
for key, _ := range m {
if sb.Len() > 1 {
sb.WriteString(fmt.Sprintf(", %q: func(){}", key))
} else {
sb.WriteString(fmt.Sprintf("%q: func(){}", key))
}
}
sb.WriteByte('}')
return sb.String()
}
func (dc *dataCursor) String() string {
return "$(...)"
return "$()"
/* var sb strings.Builder
sb.WriteString(fmt.Sprintf(`$(
index: %d,
ds: %s,
ctx: `, dc.index, mapToString(dc.ds)))
CtxToBuilder(&sb, dc.ctx, 1)
sb.WriteByte(')')
return sb.String()
*/
}
func (dc *dataCursor) HasOperation(name string) (exists bool) {
f, ok := dc.ds[name]
exists = ok && isFunctor(f)
return
}
func (dc *dataCursor) CallOperation(name string, args []any) (value any, err error) {
if functor, ok := dc.ds[name]; ok && isFunctor(functor) {
if functor == dc.cleanFunc {
return nil, dc.Clean()
} else if functor == dc.resetFunc {
return nil, dc.Reset()
} else {
ctx := cloneContext(dc.ctx)
value, err = functor.Invoke(ctx, name, []any{})
exportObjects(dc.ctx, ctx)
}
} else {
err = errNoOperation(name)
}
return
}
func (dc *dataCursor) Reset() (err error) {
if dc.resetFunc != nil {
ctx := cloneContext(dc.ctx)
if _, err = dc.resetFunc.Invoke(ctx, resetName, []any{}); err == nil {
dc.index = -1
if dc.resource != nil {
ctx := cloneContext(dc.ctx)
if _, err = dc.resetFunc.Invoke(ctx, resetName, []any{dc.resource}); err == nil {
dc.index = -1
}
exportObjects(dc.ctx, ctx)
} else {
err = errInvalidDataSource()
}
exportObjects(dc.ctx, ctx)
} else {
err = errors.New("no 'reset' function defined in the data-source")
err = errNoOperation(resetName)
}
return
}
func (dc *dataCursor) Clean() (err error) {
if dc.cleanFunc != nil {
ctx := cloneContext(dc.ctx)
_, err = dc.cleanFunc.Invoke(ctx, cleanName, []any{})
exportObjects(dc.ctx, ctx)
if dc.resource != nil {
ctx := cloneContext(dc.ctx)
_, err = dc.cleanFunc.Invoke(ctx, cleanName, []any{dc.resource})
dc.resource = nil
exportObjects(dc.ctx, ctx)
} else {
err = errInvalidDataSource()
}
} else {
err = errors.New("no 'clean' function defined in the data-source")
}
@ -74,15 +133,22 @@ func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at
}
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item
ctx := cloneContext(dc.ctx)
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{}); err == nil {
if item == nil {
err = io.EOF
} else {
dc.index++
if dc.resource != nil {
ctx := cloneContext(dc.ctx)
// fmt.Printf("Entering Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil {
if item == nil {
err = io.EOF
} else {
dc.index++
}
}
// fmt.Printf("Exiting Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
exportObjects(dc.ctx, ctx)
// fmt.Printf("Outer-Ctx [%p]: %s\n", dc.ctx, CtxToString(dc.ctx, 0))
} else {
err = errInvalidDataSource()
}
exportObjects(dc.ctx, ctx)
return
}

View File

@ -1,17 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-common.go
package expr
import (
"fmt"
)
func errOneParam(funcName string) error {
return fmt.Errorf("%s() requires exactly one param", funcName)
}
func errCantConvert(funcName string, value any, kind string) error {
return fmt.Errorf("%s() can't convert %T to %s", funcName, value, kind)
}

View File

@ -10,7 +10,7 @@ import (
)
func checkNumberParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(isNumber(paramValue) || isList(paramValue)) {
if !(isNumber(paramValue) || isList(paramValue) || isIterator(paramValue)) {
err = fmt.Errorf("%s(): param nr %d has wrong type %T, number expected", funcName, paramPos+1, paramValue)
}
return
@ -27,6 +27,11 @@ func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
if v, err = doAdd(ctx, name, subIter); err != nil {
break
}
if subIter.HasOperation(cleanName) {
if _, err = subIter.CallOperation(cleanName, nil); err != nil {
return
}
}
} else {
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
break
@ -77,7 +82,7 @@ func doMul(ctx ExprContext, name string, it Iterator) (result any, err error) {
}
if array, ok := v.([]any); ok {
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
if v, err = doMul(ctx, name, NewFlatArrayIterator(array)); err != nil {
break
}
}

View File

@ -126,7 +126,7 @@ func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err er
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle
result = nil
if len(args) > 0 {
handle, _ = args[0].(osHandle)
}
@ -141,13 +141,16 @@ func readFileFunc(ctx ExprContext, name string, args []any) (result any, err err
limit = s[0]
}
}
if v, err = r.reader.ReadString(limit); err == nil || err == io.EOF {
if v, err = r.reader.ReadString(limit); err == nil {
if len(v) > 0 && v[len(v)-1] == limit {
result = v[0 : len(v)-1]
} else {
result = v
}
}
if err == io.EOF {
err = nil
}
}
}
}

118
func-string.go Normal file
View File

@ -0,0 +1,118 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-string.go
package expr
import (
"io"
"strings"
)
// --- Start of function definitions
func doJoinStr(funcName string, sep string, it Iterator) (result any, err error) {
var sb strings.Builder
var v any
for v, err = it.Next(); err == nil; v, err = it.Next() {
if it.Index() > 0 {
sb.WriteString(sep)
}
if s, ok := v.(string); ok {
sb.WriteString(s)
} else {
err = errExpectedGot(funcName, typeString, v)
return
}
}
if err == nil || err == io.EOF {
err = nil
result = sb.String()
}
return
}
func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if len(args) < 1 {
return nil, errMissingRequiredParameter(name, paramSeparator)
}
if sep, ok := args[0].(string); ok {
if len(args) == 1 {
result = ""
} else if len(args) == 2 {
if ls, ok := args[1].([]any); ok {
result, err = doJoinStr(name, sep, NewFlatArrayIterator(ls))
} else if it, ok := args[1].(Iterator); ok {
result, err = doJoinStr(name, sep, it)
} else {
err = errInvalidParameterValue(name, paramParts, args[1])
}
} else {
result, err = doJoinStr(name, sep, NewFlatArrayIterator(args[1:]))
}
} else {
err = errWrongParamType(name, paramSeparator, typeString, args[0])
}
return
}
func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var start = 0
var count = -1
var source string
var ok bool
if len(args) < 1 {
return nil, errMissingRequiredParameter(name, paramSource)
}
if source, ok = args[0].(string); !ok {
return nil, errWrongParamType(name, paramSource, typeString, args[0])
}
if len(args) > 1 {
if start, err = toInt(args[1], name+"()"); err != nil {
return
}
if len(args) > 2 {
if count, err = toInt(args[2], name+"()"); err != nil {
return
}
}
if start < 0 {
start = len(source) + start
}
}
if count < 0 {
count = len(source) - start
}
end := min(start+count, len(source))
result = source[start:end]
return
}
func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var source string
var ok bool
if len(args) < 1 {
return nil, errMissingRequiredParameter(name, paramSource)
}
if source, ok = args[0].(string); !ok {
return nil, errWrongParamType(name, paramSource, typeString, args[0])
}
result = strings.TrimSpace(source)
return
}
// --- End of function definitions
// Import above functions in the context
func ImportStringFuncs(ctx ExprContext) {
ctx.RegisterFunc("joinStr", &simpleFunctor{f: joinStrFunc}, 1, -1)
ctx.RegisterFunc("subStr", &simpleFunctor{f: subStrFunc}, 1, -1)
ctx.RegisterFunc("trimStr", &simpleFunctor{f: trimStrFunc}, 1, -1)
}
// Register the import function in the import-register.
// That will allow to import all function of this module by the "builtin" operator."
func init() {
registerImport("string", ImportStringFuncs, "string utilities")
}

View File

@ -15,6 +15,14 @@ func NewFlatArrayIterator(array []any) *FlatArrayIterator {
return &FlatArrayIterator{a: array, index: 0}
}
func (it *FlatArrayIterator) HasOperation(name string) bool {
return false
}
func (it *FlatArrayIterator) CallOperation(name string, args []any) (any, error) {
return nil, errNoOperation(name)
}
func (it *FlatArrayIterator) Current() (item any, err error) {
if it.index >= 0 && it.index < len(it.a) {
item = it.a[it.index]

View File

@ -4,8 +4,23 @@
// iterator.go
package expr
import (
"errors"
"fmt"
)
type Iterator interface {
Next() (item any, err error) // must return io.EOF after the last item
Current() (item any, err error)
Index() int
HasOperation(name string) bool
CallOperation(name string, args []any) (value any, err error)
}
func errNoOperation(name string) error {
return fmt.Errorf("no %q function defined in the data-source", name)
}
func errInvalidDataSource() error {
return errors.New("invalid data-source")
}

View File

@ -24,6 +24,7 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
ctx := cloneContext(parentCtx)
name, _ := self.tk.Value.(string)
// fmt.Printf("Call %s(), context: %p\n", name, ctx)
params := make([]any, len(self.children))
for i, tree := range self.children {
var param any

View File

@ -6,6 +6,8 @@ package expr
import (
"fmt"
"slices"
"strings"
)
// -------- iterator term
@ -41,6 +43,44 @@ func evalTermArray(ctx ExprContext, a []*term) (values []any, err error) {
return
}
// func getDataSourceDict(ctx ExprContext, self *term) (ds map[string]Functor, err error) {
// var value any
// if len(self.children) < 1 || self.children[0] == nil {
// err = self.Errorf("missing the data-source parameter")
// return
// }
// if value, err = self.children[0].compute(ctx); err != nil {
// return
// }
// if dictAny, ok := value.(map[any]any); ok {
// ds = make(map[string]Functor)
// // required functions
// for _, k := range []string{currentName, nextName} {
// if item, exists := dictAny[k]; exists && item != nil {
// if functor, ok := item.(*funcDefFunctor); ok {
// ds[k] = functor
// }
// } else {
// err = fmt.Errorf("the data-source must provide a non-nil %q operator", k)
// return
// }
// }
// // Optional functions
// for _, k := range []string{initName, resetName, cleanName} {
// if item, exists := dictAny[k]; exists && item != nil {
// if functor, ok := item.(*funcDefFunctor); ok {
// ds[k] = functor
// }
// }
// }
// } else {
// err = self.Errorf("the first param (data-source) of an iterator must be a dict, not a %T", value)
// }
// return
// }
func getDataSourceDict(ctx ExprContext, self *term) (ds map[string]Functor, err error) {
var value any
if len(self.children) < 1 || self.children[0] == nil {
@ -52,26 +92,30 @@ func getDataSourceDict(ctx ExprContext, self *term) (ds map[string]Functor, err
return
}
requiredFields := []string{currentName, nextName}
fieldsMask := 0b11
foundFields := 0
if dictAny, ok := value.(map[any]any); ok {
ds = make(map[string]Functor)
// required functions
for _, k := range []string{currentName, nextName} {
if item, exists := dictAny[k]; exists && item != nil {
for keyAny, item := range dictAny {
if key, ok := keyAny.(string); ok {
if functor, ok := item.(*funcDefFunctor); ok {
ds[k] = functor
ds[key] = functor
if index := slices.Index(requiredFields, key); index >= 0 {
foundFields |= 1 << index
}
}
} else {
err = fmt.Errorf("the data-source must provide a non-nil %q operator", k)
return
}
}
// Optional functions
for _, k := range []string{initName, resetName, cleanName} {
if item, exists := dictAny[k]; exists && item != nil {
if functor, ok := item.(*funcDefFunctor); ok {
ds[k] = functor
// check required functions
if foundFields != fieldsMask {
missingFields := make([]string, 0, len(requiredFields))
for index, field := range requiredFields {
if (foundFields & (1 << index)) == 0 {
missingFields = append(missingFields, field)
}
}
err = fmt.Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", "))
}
} else {
err = self.Errorf("the first param (data-source) of an iterator must be a dict, not a %T", value)
@ -86,8 +130,9 @@ func evalIterator(ctx ExprContext, self *term) (v any, err error) {
return
}
dc := newDataCursor(ctx)
dc := newDataCursor(ctx, ds)
// var it Iterator = dc
// fmt.Println(it)
if initFunc, exists := ds[initName]; exists && initFunc != nil {
var args []any
if len(self.children) > 1 {

View File

@ -44,9 +44,11 @@ func NewSimpleFuncStore() *SimpleFuncStore {
}
func (ctx *SimpleFuncStore) Clone() ExprContext {
svs := ctx.SimpleVarStore
return &SimpleFuncStore{
SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
funcStore: CloneMap(ctx.funcStore),
// SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
SimpleVarStore: SimpleVarStore{varStore: svs.cloneVars()},
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
}
}

View File

@ -4,7 +4,10 @@
// simple-var-store.go
package expr
import "fmt"
import (
"fmt"
"strings"
)
type SimpleVarStore struct {
varStore map[string]any
@ -16,9 +19,14 @@ func NewSimpleVarStore() *SimpleVarStore {
}
}
func (ctx *SimpleVarStore) cloneVars() (vars map[string]any) {
return CloneFilteredMap(ctx.varStore, func(name string) bool { return name[0] != '@' })
}
func (ctx *SimpleVarStore) Clone() (clone ExprContext) {
// fmt.Println("*** Cloning context ***")
clone = &SimpleVarStore{
varStore: CloneMap(ctx.varStore),
varStore: ctx.cloneVars(),
}
return clone
}
@ -29,10 +37,12 @@ func (ctx *SimpleVarStore) GetVar(varName string) (v any, exists bool) {
}
func (ctx *SimpleVarStore) setVar(varName string, value any) {
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
ctx.varStore[varName] = value
}
func (ctx *SimpleVarStore) SetVar(varName string, value any) {
// fmt.Printf("[%p] SetVar(%v, %v)\n", ctx, varName, value)
if allowedValue, ok := fromGenericAny(value); ok {
ctx.varStore[varName] = allowedValue
} else {
@ -68,3 +78,35 @@ func (ctx *SimpleVarStore) RegisterFunc(name string, functor Functor, minArgs, m
func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
return
}
func CtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("{\n")
first := true
for _, name := range ctx.EnumVars(func(name string) bool { return name[0] != '_' }) {
value, _ := ctx.GetVar(name)
sb.WriteString(strings.Repeat("\t", indent+1))
sb.WriteString(name)
sb.WriteString("=")
if _, ok := value.(Functor); ok {
sb.WriteString(": func(){}")
} else if _, ok = value.(map[any]any); ok {
sb.WriteString("dict{}")
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
if first {
first = false
} else {
sb.WriteByte(',')
}
sb.WriteByte('\n')
}
sb.WriteString(strings.Repeat("\t", indent))
sb.WriteString("}\n")
}
func CtxToString(ctx ExprContext, indent int) string {
var sb strings.Builder
CtxToBuilder(&sb, ctx, indent)
return sb.String()
}

View File

@ -154,7 +154,7 @@ func (self *term) toInt(computedValue any, valueDescription string) (i int, err
if index64, ok := computedValue.(int64); ok {
i = int(index64)
} else {
err = self.Errorf("%s, got %T", valueDescription, computedValue)
err = self.Errorf("%s, got %T (%v)", valueDescription, computedValue, computedValue)
}
return
}

71
tools/func-bolerplate.bash Executable file
View File

@ -0,0 +1,71 @@
#!/usr/bin/env bash
if [ $# -lt 2 ]; then
echo >&2 "Usage: $(basename "${0}") <module-name> <func-name>..."
exit 1
fi
MODULE_NAME=${1,,}
GO_FILE="func-${MODULE_NAME//[-.]/_}.go"
shift
FUNC_LIST=
i=0
for name; do
if [ ${i} -gt 0 ]; then
if [ $((i+1)) -eq $# ]; then
FUNC_LIST+=" and "
else
FUNC_LIST+=", "
fi
fi
FUNC_LIST+="${name}()"
((i++))
done
cat > "${GO_FILE}" <<EOF
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-${MODULE_NAME}.go
package expr
import (
)
// --- Start of function definitions
$(
for name; do
cat <<IEOF
func ${name}Func(ctx ExprContext, name string, args []any) (result any, err error) {
return
}
IEOF
done
)
// --- End of function definitions
// Import above functions in the context
func Import${MODULE_NAME^}Funcs(ctx ExprContext) {
$(
for name; do
cat <<IEOF
ctx.RegisterFunc("${name}", &simpleFunctor{f: ${name}Func}, 1, -1)
IEOF
done
)
}
// Register the import function in the import-register.
// That will allow to import all function of this module by the "builtin" operator."
func init() {
registerImport("${MODULE_NAME}", Import${name}Funcs, "The \"${MODULE_NAME}\" module implements the ${FUNC_LIST} function(s)")
}
EOF

View File

@ -4,7 +4,10 @@
// utils.go
package expr
import "reflect"
import (
"fmt"
"reflect"
)
func isString(v any) (ok bool) {
_, ok = v.(string)
@ -39,6 +42,16 @@ func isNumberString(v any) (ok bool) {
return isString(v) || isNumber(v)
}
func isFunctor(v any) (ok bool) {
_, ok = v.(Functor)
return
}
func isIterator(v any) (ok bool) {
_, ok = v.(Iterator)
return
}
func numAsFloat(v any) (f float64) {
var ok bool
if f, ok = v.(float64); !ok {
@ -136,3 +149,32 @@ func CloneMap[K comparable, V any](source map[K]V) map[K]V {
dest := make(map[K]V, len(source))
return CopyMap(dest, source)
}
func CopyFilteredMap[K comparable, V any](dest, source map[K]V, filter func(key K) (accept bool)) map[K]V {
// fmt.Printf("--- Clone with filter %p\n", filter)
if filter == nil {
return CopyMap(dest, source)
} else {
for k, v := range source {
if filter(k) {
// fmt.Printf("\tClone var %q\n", k)
dest[k] = v
}
}
}
return dest
}
func CloneFilteredMap[K comparable, V any](source map[K]V, filter func(key K) (accept bool)) map[K]V {
dest := make(map[K]V, len(source))
return CopyFilteredMap(dest, source, filter)
}
func toInt(value any, description string) (i int, err error) {
if valueInt64, ok := value.(int64); ok {
i = int(valueInt64)
} else {
err = fmt.Errorf("%s expected integer, got %T (%v)", description, value, value)
}
return
}