diff --git a/bind-expr-functions.go b/bind-expr-functions.go index 02427d3..99a6dd0 100644 --- a/bind-expr-functions.go +++ b/bind-expr-functions.go @@ -12,19 +12,14 @@ type exprFunctor struct { defCtx ExprContext } -// func newExprFunctor(e Expr, params []string, ctx ExprContext) *exprFunctor { -// return &exprFunctor{expr: e, params: params, defCtx: ctx} -// } +func (functor *exprFunctor) GetParams() (params []ExprFuncParam) { + return functor.params +} func newExprFunctor(e Expr, params []ExprFuncParam, ctx ExprContext) *exprFunctor { var defCtx ExprContext if ctx != nil { -// if ctx.GetParent() != nil { -// defCtx = ctx.Clone() -// defCtx.SetParent(ctx) -// } else { - defCtx = ctx -// } + defCtx = ctx } return &exprFunctor{expr: e, params: params, defCtx: defCtx} } @@ -37,14 +32,10 @@ func (functor *exprFunctor) GetDefinitionContext() ExprContext { return functor.defCtx } -func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) { - // if functor.defCtx != nil { - // ctx.Merge(functor.defCtx) - // } - - for i, p := range functor.params { - if i < len(args) { - arg := args[i] +func (functor *exprFunctor) InvokeNamed(ctx ExprContext, name string, args map[string]any) (result any, err error) { + var missing []string + for _, p := range functor.params { + if arg, exists := args[p.Name()]; exists { if funcArg, ok := arg.(Functor); ok { paramSpecs := funcArg.GetParams() ctx.RegisterFunc(p.Name(), funcArg, TypeAny, paramSpecs) @@ -52,9 +43,17 @@ func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (re ctx.UnsafeSetVar(p.Name(), arg) } } else { - ctx.UnsafeSetVar(p.Name(), nil) + if missing == nil { + missing = make([]string, 0, 1) + } + missing = append(missing, p.Name()) + // ctx.UnsafeSetVar(p.Name(), nil) } } - result, err = functor.expr.Eval(ctx) + if missing != nil { + err = ErrMissingParams(name, missing) + } else { + result, err = functor.expr.Eval(ctx) + } return } diff --git a/bind-go-functions.go b/bind-go-functions.go index c120f5f..0264d80 100644 --- a/bind-go-functions.go +++ b/bind-go-functions.go @@ -10,7 +10,7 @@ type golangFunctor struct { f FuncTemplate } -func NewGolangFunctor(f FuncTemplate) *golangFunctor { +func NewGolangFunctor2(f FuncTemplate) *golangFunctor { return &golangFunctor{f: f} } @@ -18,6 +18,6 @@ func (functor *golangFunctor) TypeName() string { return "GoFunctor" } -func (functor *golangFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) { +func (functor *golangFunctor) InvokeNamed(ctx ExprContext, name string, args map[string]any) (result any, err error) { return functor.f(ctx, name, args) } diff --git a/builtin-base.go b/builtin-base.go index 4563feb..aa73807 100644 --- a/builtin-base.go +++ b/builtin-base.go @@ -10,53 +10,57 @@ import ( "strconv" ) -func isNilFunc(ctx ExprContext, name string, args []any) (result any, err error) { - result = args[0] == nil +const ( + ParamDenominator = "denominator" +) + +func isNilFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + result = args[ParamValue] == nil return } -func isIntFunc(ctx ExprContext, name string, args []any) (result any, err error) { - result = IsInteger(args[0]) +func isIntFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + result = IsInteger(args[ParamValue]) return } -func isFloatFunc(ctx ExprContext, name string, args []any) (result any, err error) { - result = IsFloat(args[0]) +func isFloatFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + result = IsFloat(args[ParamValue]) return } -func isBoolFunc(ctx ExprContext, name string, args []any) (result any, err error) { - result = IsBool(args[0]) +func isBoolFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + result = IsBool(args[ParamValue]) return } -func isStringFunc(ctx ExprContext, name string, args []any) (result any, err error) { - result = IsString(args[0]) +func isStringFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + result = IsString(args[ParamValue]) return } -func isFractionFunc(ctx ExprContext, name string, args []any) (result any, err error) { - result = IsFract(args[0]) +func isFractionFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + result = IsFract(args[ParamValue]) return } -func isRationalFunc(ctx ExprContext, name string, args []any) (result any, err error) { - result = IsRational(args[0]) +func isRationalFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + result = IsRational(args[ParamValue]) return } -func isListFunc(ctx ExprContext, name string, args []any) (result any, err error) { - result = IsList(args[0]) +func isListFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + result = IsList(args[ParamValue]) return } -func isDictionaryFunc(ctx ExprContext, name string, args []any) (result any, err error) { - result = IsDict(args[0]) +func isDictionaryFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + result = IsDict(args[ParamValue]) return } -func boolFunc(ctx ExprContext, name string, args []any) (result any, err error) { - switch v := args[0].(type) { +func boolFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + switch v := args[ParamValue].(type) { case int64: result = (v != 0) case *FractionType: @@ -73,8 +77,8 @@ func boolFunc(ctx ExprContext, name string, args []any) (result any, err error) return } -func intFunc(ctx ExprContext, name string, args []any) (result any, err error) { - switch v := args[0].(type) { +func intFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + switch v := args[ParamValue].(type) { case int64: result = v case float64: @@ -96,8 +100,8 @@ func intFunc(ctx ExprContext, name string, args []any) (result any, err error) { return } -func decFunc(ctx ExprContext, name string, args []any) (result any, err error) { - switch v := args[0].(type) { +func decFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + switch v := args[ParamValue].(type) { case int64: result = float64(v) case float64: @@ -121,8 +125,8 @@ func decFunc(ctx ExprContext, name string, args []any) (result any, err error) { return } -func stringFunc(ctx ExprContext, name string, args []any) (result any, err error) { - switch v := args[0].(type) { +func stringFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + switch v := args[ParamValue].(type) { case int64: result = strconv.FormatInt(v, 10) case float64: @@ -147,18 +151,18 @@ func stringFunc(ctx ExprContext, name string, args []any) (result any, err error return } -func fractFunc(ctx ExprContext, name string, args []any) (result any, err error) { - switch v := args[0].(type) { +func fractFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + switch v := args[ParamValue].(type) { case int64: var den int64 = 1 - if len(args) > 1 { - var ok bool - if den, ok = args[1].(int64); !ok { - err = ErrExpectedGot(name, "integer", args[1]) - } else if den == 0 { - err = ErrFuncDivisionByZero(name) - } + + var ok bool + if den, ok = args[ParamDenominator].(int64); !ok { + err = ErrExpectedGot(name, "integer", args[ParamDenominator]) + } else if den == 0 { + err = ErrFuncDivisionByZero(name) } + if err == nil { result = newFraction(v, den) } @@ -189,23 +193,23 @@ func ImportBuiltinsFuncs(ctx ExprContext) { NewFuncParam(ParamValue), } - ctx.RegisterFunc("isNil", NewGolangFunctor(isNilFunc), TypeBoolean, anyParams) - ctx.RegisterFunc("isInt", NewGolangFunctor(isIntFunc), TypeBoolean, anyParams) - ctx.RegisterFunc("isFloat", NewGolangFunctor(isFloatFunc), TypeBoolean, anyParams) - ctx.RegisterFunc("isBool", NewGolangFunctor(isBoolFunc), TypeBoolean, anyParams) - ctx.RegisterFunc("isString", NewGolangFunctor(isStringFunc), TypeBoolean, anyParams) - ctx.RegisterFunc("isFract", NewGolangFunctor(isFractionFunc), TypeBoolean, anyParams) - ctx.RegisterFunc("isRational", NewGolangFunctor(isRationalFunc), TypeBoolean, anyParams) - ctx.RegisterFunc("isList", NewGolangFunctor(isListFunc), TypeBoolean, anyParams) - ctx.RegisterFunc("isDict", NewGolangFunctor(isDictionaryFunc), TypeBoolean, anyParams) + ctx.RegisterFunc("isNil", NewGolangFunctor2(isNilFunc), TypeBoolean, anyParams) + ctx.RegisterFunc("isInt", NewGolangFunctor2(isIntFunc), TypeBoolean, anyParams) + ctx.RegisterFunc("isFloat", NewGolangFunctor2(isFloatFunc), TypeBoolean, anyParams) + ctx.RegisterFunc("isBool", NewGolangFunctor2(isBoolFunc), TypeBoolean, anyParams) + ctx.RegisterFunc("isString", NewGolangFunctor2(isStringFunc), TypeBoolean, anyParams) + ctx.RegisterFunc("isFract", NewGolangFunctor2(isFractionFunc), TypeBoolean, anyParams) + ctx.RegisterFunc("isRational", NewGolangFunctor2(isRationalFunc), TypeBoolean, anyParams) + ctx.RegisterFunc("isList", NewGolangFunctor2(isListFunc), TypeBoolean, anyParams) + ctx.RegisterFunc("isDict", NewGolangFunctor2(isDictionaryFunc), TypeBoolean, anyParams) - ctx.RegisterFunc("bool", NewGolangFunctor(boolFunc), TypeBoolean, anyParams) - ctx.RegisterFunc("int", NewGolangFunctor(intFunc), TypeInt, anyParams) - ctx.RegisterFunc("dec", NewGolangFunctor(decFunc), TypeFloat, anyParams) - ctx.RegisterFunc("string", NewGolangFunctor(stringFunc), TypeString, anyParams) - ctx.RegisterFunc("fract", NewGolangFunctor(fractFunc), TypeFraction, []ExprFuncParam{ + ctx.RegisterFunc("bool", NewGolangFunctor2(boolFunc), TypeBoolean, anyParams) + ctx.RegisterFunc("int", NewGolangFunctor2(intFunc), TypeInt, anyParams) + ctx.RegisterFunc("dec", NewGolangFunctor2(decFunc), TypeFloat, anyParams) + ctx.RegisterFunc("string", NewGolangFunctor2(stringFunc), TypeString, anyParams) + ctx.RegisterFunc("fract", NewGolangFunctor2(fractFunc), TypeFraction, []ExprFuncParam{ NewFuncParam(ParamValue), - NewFuncParamFlagDef("denominator", PfDefault, 1), + NewFuncParamFlagDef(ParamDenominator, PfDefault, int64(1)), }) } diff --git a/builtin-fmt.go b/builtin-fmt.go index 1d79ba0..a068e9b 100644 --- a/builtin-fmt.go +++ b/builtin-fmt.go @@ -21,27 +21,33 @@ func getStdout(ctx ExprContext) io.Writer { return w } -func printFunc(ctx ExprContext, name string, args []any) (result any, err error) { - var n int - if n, err = fmt.Fprint(getStdout(ctx), args...); err == nil { - result = int64(n) +func printFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + var n int = 0 + if v, exists := args[ParamItem]; exists && v != nil { + argv := v.([]any) + n, err = fmt.Fprint(getStdout(ctx), argv...) } + result = int64(n) return } -func printLnFunc(ctx ExprContext, name string, args []any) (result any, err error) { - var n int - if n, err = fmt.Fprintln(getStdout(ctx), args...); err == nil { - result = int64(n) +func printLnFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + var n int = 0 + if v, exists := args[ParamItem]; exists && v != nil { + argv := v.([]any) + n, err = fmt.Fprintln(getStdout(ctx), argv...) + } else { + n, err = fmt.Fprintln(getStdout(ctx)) } + result = int64(n) return } func ImportFmtFuncs(ctx ExprContext) { - ctx.RegisterFunc("print", NewGolangFunctor(printFunc), TypeInt, []ExprFuncParam{ + ctx.RegisterFunc("print", NewGolangFunctor2(printFunc), TypeInt, []ExprFuncParam{ NewFuncParamFlag(ParamItem, PfRepeat), }) - ctx.RegisterFunc("println", NewGolangFunctor(printLnFunc), TypeInt, []ExprFuncParam{ + ctx.RegisterFunc("println", NewGolangFunctor2(printLnFunc), TypeInt, []ExprFuncParam{ NewFuncParamFlag(ParamItem, PfRepeat), }) } diff --git a/builtin-import.go b/builtin-import.go index 5afd4c6..12428ed 100644 --- a/builtin-import.go +++ b/builtin-import.go @@ -9,18 +9,21 @@ import ( "os" ) -func importFunc(ctx ExprContext, name string, args []any) (result any, err error) { +func importFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { return importGeneral(ctx, name, args) } -func importAllFunc(ctx ExprContext, name string, args []any) (result any, err error) { +func importAllFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { CtrlEnable(ctx, control_export_all) return importGeneral(ctx, name, args) } -func importGeneral(ctx ExprContext, name string, args []any) (result any, err error) { +func importGeneral(ctx ExprContext, name string, args map[string]any) (result any, err error) { dirList := buildSearchDirList("sources", ENV_EXPR_SOURCE_PATH) - result, err = doImport(ctx, name, dirList, NewArrayIterator(args)) + if v, exists := args[ParamFilepath]; exists && v != nil { + argv := v.([]any) + result, err = doImport(ctx, name, dirList, NewArrayIterator(argv)) + } return } @@ -62,10 +65,10 @@ func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (resu } func ImportImportFuncs(ctx ExprContext) { - ctx.RegisterFunc("import", NewGolangFunctor(importFunc), TypeAny, []ExprFuncParam{ + ctx.RegisterFunc("import", NewGolangFunctor2(importFunc), TypeAny, []ExprFuncParam{ NewFuncParamFlag(ParamFilepath, PfRepeat), }) - ctx.RegisterFunc("importAll", NewGolangFunctor(importAllFunc), TypeAny, []ExprFuncParam{ + ctx.RegisterFunc("importAll", NewGolangFunctor2(importAllFunc), TypeAny, []ExprFuncParam{ NewFuncParamFlag(ParamFilepath, PfRepeat), }) } diff --git a/builtin-iterator.go b/builtin-iterator.go index 4f8f80a..d99f367 100644 --- a/builtin-iterator.go +++ b/builtin-iterator.go @@ -15,43 +15,44 @@ const ( iterVarStatus = "status" ) -func parseRunArgs(localCtx ExprContext, args []any) (it Iterator, op Functor, err error) { +func parseRunArgs(localCtx ExprContext, args map[string]any) (it Iterator, op Functor, err error) { var ok bool - if it, ok = args[0].(Iterator); !ok { - err = fmt.Errorf("paramter %q must be an iterator, passed %v [%s]", ParamIterator, args[0], TypeName(args[0])) + if it, ok = args[ParamIterator].(Iterator); !ok { + err = fmt.Errorf("paramter %q must be an iterator, passed %v [%s]", ParamIterator, args[ParamIterator], TypeName(args[ParamIterator])) return } - if len(args) > 1 { - if op, ok = args[1].(Functor); !ok || op == nil { - err = fmt.Errorf("paramter %q must be a function, passed %v [%s]", iterParamOperator, args[1], TypeName(args[1])) - return - } - if len(args) > 2 { - var vars *DictType - if vars, ok = args[2].(*DictType); !ok || vars == nil { - err = fmt.Errorf("paramter %q must be a dictionary, passed %v [%s]", iterParamVars, args[2], TypeName(args[2])) - return - } - for key, value := range *vars { - var varName string - if varName, ok = key.(string); ok { - localCtx.UnsafeSetVar(varName, value) - } + if op, ok = args[iterParamOperator].(Functor); !ok && args[iterParamOperator] != nil { + err = fmt.Errorf("paramter %q must be a function, passed %v [%s]", iterParamOperator, args[iterParamOperator], TypeName(args[iterParamOperator])) + return + } + + var vars *DictType + if vars, ok = args[iterParamVars].(*DictType); !ok && args[iterParamVars] != nil { + err = fmt.Errorf("paramter %q must be a dictionary, passed %v [%s]", iterParamVars, args[iterParamVars], TypeName(args[iterParamVars])) + return + } + + if vars != nil { + for key, value := range *vars { + var varName string + if varName, ok = key.(string); ok { + localCtx.UnsafeSetVar(varName, value) } } } + return } -func runFunc(ctx ExprContext, name string, args []any) (result any, err error) { +func runFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { var it Iterator var ok bool var op Functor var v any var usingDefaultOp = false - var params []any + var params map[string]any var item any localCtx := ctx.Clone() @@ -60,18 +61,18 @@ func runFunc(ctx ExprContext, name string, args []any) (result any, err error) { if it, op, err = parseRunArgs(localCtx, args); err != nil { return } else if op == nil { - op = NewGolangFunctor(printLnFunc) + op = NewGolangFunctor2(printLnFunc) usingDefaultOp = true } for item, err = it.Next(); err == nil; item, err = it.Next() { if usingDefaultOp { - params = []any{item} + params = map[string]any{ParamItem: []any{item}} } else { - params = []any{it.Index(), item} + params = map[string]any{ParamIndex: it.Index(), ParamItem: item} } - if v, err = op.Invoke(localCtx, iterParamOperator, params); err != nil { + if v, err = op.InvokeNamed(localCtx, iterParamOperator, params); err != nil { break } else { var success bool @@ -91,7 +92,7 @@ func runFunc(ctx ExprContext, name string, args []any) (result any, err error) { } func ImportIterFuncs(ctx ExprContext) { - ctx.RegisterFunc("run", NewGolangFunctor(runFunc), TypeAny, []ExprFuncParam{ + ctx.RegisterFunc("run", NewGolangFunctor2(runFunc), TypeAny, []ExprFuncParam{ NewFuncParam(ParamIterator), NewFuncParamFlag(iterParamOperator, PfOptional), NewFuncParamFlag(iterParamVars, PfOptional), diff --git a/builtin-math-arith.go b/builtin-math-arith.go index 53d9bb8..baf7a46 100644 --- a/builtin-math-arith.go +++ b/builtin-math-arith.go @@ -86,8 +86,9 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result return } -func addFunc(ctx ExprContext, name string, args []any) (result any, err error) { - result, err = doAdd(ctx, name, NewArrayIterator(args), 0, -1) +func addFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + argv := args[ParamValue].([]any) + result, err = doAdd(ctx, name, NewArrayIterator(argv), 0, -1) return } @@ -161,17 +162,18 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result return } -func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) { - result, err = doMul(ctx, name, NewArrayIterator(args), 0, -1) +func mulFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + argv := args[ParamValue].([]any) + result, err = doMul(ctx, name, NewArrayIterator(argv), 0, -1) return } func ImportMathFuncs(ctx ExprContext) { - ctx.RegisterFunc("add", &golangFunctor{f: addFunc}, TypeNumber, []ExprFuncParam{ + ctx.RegisterFunc("add", NewGolangFunctor2(addFunc), TypeNumber, []ExprFuncParam{ NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, int64(0)), }) - ctx.RegisterFunc("mul", &golangFunctor{f: mulFunc}, TypeNumber, []ExprFuncParam{ + ctx.RegisterFunc("mul", NewGolangFunctor2(mulFunc), TypeNumber, []ExprFuncParam{ NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, int64(1)), }) } diff --git a/builtin-os-file.go b/builtin-os-file.go index 8b35311..fa377f1 100644 --- a/builtin-os-file.go +++ b/builtin-os-file.go @@ -11,6 +11,10 @@ import ( "os" ) +const ( + osLimitCh = "limitCh" +) + type osHandle interface { getFile() *os.File } @@ -61,8 +65,8 @@ func errInvalidFileHandle(funcName string, v any) error { } } -func createFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { - if filePath, ok := args[0].(string); ok && len(filePath) > 0 { +func createFileFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + if filePath, ok := args[ParamFilepath].(string); ok && len(filePath) > 0 { var fh *os.File if fh, err = os.Create(filePath); err == nil { result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)} @@ -73,8 +77,8 @@ func createFileFunc(ctx ExprContext, name string, args []any) (result any, err e return } -func openFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { - if filePath, ok := args[0].(string); ok && len(filePath) > 0 { +func openFileFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + if filePath, ok := args[ParamFilepath].(string); ok && len(filePath) > 0 { var fh *os.File if fh, err = os.Open(filePath); err == nil { result = &osReader{fh: fh, reader: bufio.NewReader(fh)} @@ -85,8 +89,8 @@ func openFileFunc(ctx ExprContext, name string, args []any) (result any, err err return } -func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { - if filePath, ok := args[0].(string); ok && len(filePath) > 0 { +func appendFileFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + if filePath, ok := args[ParamFilepath].(string); ok && len(filePath) > 0 { var fh *os.File if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil { result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)} @@ -97,13 +101,13 @@ func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err e return } -func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { +func closeFileFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { var handle osHandle var invalidFileHandle any var ok bool - if handle, ok = args[0].(osHandle); !ok { - invalidFileHandle = args[0] + if handle, ok = args[ParamHandle].(osHandle); !ok { + invalidFileHandle = args[ParamHandle] } if handle != nil { @@ -124,18 +128,21 @@ func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err er return } -func fileWriteTextFunc(ctx ExprContext, name string, args []any) (result any, err error) { +func fileWriteTextFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { var handle osHandle var invalidFileHandle any var ok bool - if handle, ok = args[0].(osHandle); !ok { - invalidFileHandle = args[0] + if handle, ok = args[ParamHandle].(osHandle); !ok { + invalidFileHandle = args[ParamHandle] } if handle != nil { if w, ok := handle.(*osWriter); ok { - result, err = fmt.Fprint(w.writer, args[1:]...) + if v, exists := args[ParamItem]; exists { + argv := v.([]any) + result, err = fmt.Fprint(w.writer, argv...) + } } else { invalidFileHandle = handle } @@ -147,21 +154,21 @@ func fileWriteTextFunc(ctx ExprContext, name string, args []any) (result any, er return } -func fileReadTextFunc(ctx ExprContext, name string, args []any) (result any, err error) { +func fileReadTextFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { var handle osHandle var invalidFileHandle any var ok bool result = nil - if handle, ok = args[0].(osHandle); !ok || args[0] == nil { - invalidFileHandle = args[0] + if handle, ok = args[ParamHandle].(osHandle); !ok || args[ParamHandle] == nil { + invalidFileHandle = args[ParamHandle] } if handle != nil { if r, ok := handle.(*osReader); ok { var limit byte = '\n' var v string - if s, ok := args[1].(string); ok && len(s) > 0 { + if s, ok := args[osLimitCh].(string); ok && len(s) > 0 { limit = s[0] } @@ -187,14 +194,14 @@ func fileReadTextFunc(ctx ExprContext, name string, args []any) (result any, err return } -func fileReadTextAllFunc(ctx ExprContext, name string, args []any) (result any, err error) { +func fileReadTextAllFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { var handle osHandle var invalidFileHandle any var ok bool result = nil - if handle, ok = args[0].(osHandle); !ok || args[0] == nil { - invalidFileHandle = args[0] + if handle, ok = args[ParamHandle].(osHandle); !ok || args[ParamHandle] == nil { + invalidFileHandle = args[ParamHandle] } if handle != nil { @@ -214,34 +221,34 @@ func fileReadTextAllFunc(ctx ExprContext, name string, args []any) (result any, } func ImportOsFuncs(ctx ExprContext) { - ctx.RegisterFunc("fileOpen", NewGolangFunctor(openFileFunc), TypeHandle, []ExprFuncParam{ + ctx.RegisterFunc("fileOpen", NewGolangFunctor2(openFileFunc), TypeFileHandle, []ExprFuncParam{ NewFuncParam(ParamFilepath), }) - ctx.RegisterFunc("fileAppend", NewGolangFunctor(appendFileFunc), TypeHandle, []ExprFuncParam{ + ctx.RegisterFunc("fileAppend", NewGolangFunctor2(appendFileFunc), TypeFileHandle, []ExprFuncParam{ NewFuncParam(ParamFilepath), }) - ctx.RegisterFunc("fileCreate", NewGolangFunctor(createFileFunc), TypeHandle, []ExprFuncParam{ + ctx.RegisterFunc("fileCreate", NewGolangFunctor2(createFileFunc), TypeFileHandle, []ExprFuncParam{ NewFuncParam(ParamFilepath), }) - ctx.RegisterFunc("fileClose", NewGolangFunctor(closeFileFunc), TypeBoolean, []ExprFuncParam{ - NewFuncParam(TypeHandle), + ctx.RegisterFunc("fileClose", NewGolangFunctor2(closeFileFunc), TypeBoolean, []ExprFuncParam{ + NewFuncParam(ParamHandle), }) - ctx.RegisterFunc("fileWriteText", NewGolangFunctor(fileWriteTextFunc), TypeInt, []ExprFuncParam{ - NewFuncParam(TypeHandle), - NewFuncParamFlagDef(TypeItem, PfDefault|PfRepeat, ""), + ctx.RegisterFunc("fileWriteText", NewGolangFunctor2(fileWriteTextFunc), TypeInt, []ExprFuncParam{ + NewFuncParam(ParamHandle), + NewFuncParamFlagDef(ParamItem, PfDefault|PfRepeat, ""), }) - ctx.RegisterFunc("fileReadText", NewGolangFunctor(fileReadTextFunc), TypeString, []ExprFuncParam{ - NewFuncParam(TypeHandle), - NewFuncParamFlagDef("limitCh", PfDefault, "\n"), + ctx.RegisterFunc("fileReadText", NewGolangFunctor2(fileReadTextFunc), TypeString, []ExprFuncParam{ + NewFuncParam(ParamHandle), + NewFuncParamFlagDef(osLimitCh, PfDefault, "\n"), }) - ctx.RegisterFunc("fileReadTextAll", NewGolangFunctor(fileReadTextAllFunc), TypeString, []ExprFuncParam{ - NewFuncParam(TypeHandle), + ctx.RegisterFunc("fileReadTextAll", NewGolangFunctor2(fileReadTextAllFunc), TypeString, []ExprFuncParam{ + NewFuncParam(ParamHandle), }) } diff --git a/builtin-string.go b/builtin-string.go index a02b4c8..b2bf35c 100644 --- a/builtin-string.go +++ b/builtin-string.go @@ -10,6 +10,10 @@ import ( "strings" ) +const ( + strParamOther = "other" +) + // --- Start of function definitions func doJoinStr(funcName string, sep string, it Iterator) (result any, err error) { var sb strings.Builder @@ -32,45 +36,45 @@ func doJoinStr(funcName string, sep string, it Iterator) (result any, err error) 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].(*ListType); ok { - result, err = doJoinStr(name, sep, NewListIterator(ls, nil)) - } else if it, ok := args[1].(Iterator); ok { - result, err = doJoinStr(name, sep, it) +func joinStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + if sep, ok := args[ParamSeparator].(string); ok { + if v, exists := args[ParamItem]; exists { + argv := v.([]any) + if len(argv) == 1 { + if ls, ok := argv[0].(*ListType); ok { + result, err = doJoinStr(name, sep, NewListIterator(ls, nil)) + } else if it, ok := argv[0].(Iterator); ok { + result, err = doJoinStr(name, sep, it) + } else if s, ok := argv[0].(string); ok { + result = s + } else { + err = ErrInvalidParameterValue(name, ParamItem, v) + } } else { - err = ErrInvalidParameterValue(name, ParamParts, args[1]) + result, err = doJoinStr(name, sep, NewArrayIterator(argv)) } - } else { - result, err = doJoinStr(name, sep, NewArrayIterator(args[1:])) } } else { - err = ErrWrongParamType(name, ParamSeparator, TypeString, args[0]) + err = ErrWrongParamType(name, ParamSeparator, TypeString, args[ParamSeparator]) } return } -func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error) { +func subStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { var start = 0 var count = -1 var source string var ok bool - if source, ok = args[0].(string); !ok { - return nil, ErrWrongParamType(name, ParamSource, TypeString, args[0]) + if source, ok = args[ParamSource].(string); !ok { + return nil, ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource]) } - if start, err = ToGoInt(args[1], name+"()"); err != nil { + if start, err = ToGoInt(args[ParamStart], name+"()"); err != nil { return } - if count, err = ToGoInt(args[2], name+"()"); err != nil { + if count, err = ToGoInt(args[ParamCount], name+"()"); err != nil { return } @@ -86,81 +90,99 @@ func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error return } -func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err error) { +func trimStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { var source string var ok bool - if source, ok = args[0].(string); !ok { - return nil, ErrWrongParamType(name, ParamSource, TypeString, args[0]) + if source, ok = args[ParamSource].(string); !ok { + return nil, ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource]) } result = strings.TrimSpace(source) return } -func startsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err error) { - var source string +func startsWithStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + var source, prefix string var ok bool result = false - if source, ok = args[0].(string); !ok { - return result, ErrWrongParamType(name, ParamSource, TypeString, args[0]) + if source, ok = args[ParamSource].(string); !ok { + return result, ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource]) } - for i, targetSpec := range args[1:] { - if target, ok := targetSpec.(string); ok { - if strings.HasPrefix(source, target) { - result = true + + if prefix, ok = args[ParamPrefix].(string); !ok { + return result, ErrWrongParamType(name, ParamPrefix, TypeString, args[ParamPrefix]) + } + if strings.HasPrefix(source, prefix) { + result = true + } else if v, exists := args[strParamOther]; exists { + argv := v.([]any) + for i, targetSpec := range argv { + if target, ok := targetSpec.(string); ok { + if strings.HasPrefix(source, target) { + result = true + break + } + } else { + err = fmt.Errorf("target item nr %d is %s, string expected", i+1, TypeName(targetSpec)) break } - } else { - err = fmt.Errorf("target item nr %d is %T, expected string", i+1, targetSpec) - break } } return } -func endsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err error) { - var source string +func endsWithStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { + var source, suffix string var ok bool result = false - if source, ok = args[0].(string); !ok { - return result, ErrWrongParamType(name, ParamSource, TypeString, args[0]) + if source, ok = args[ParamSource].(string); !ok { + return result, ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource]) } - for i, targetSpec := range args[1:] { - if target, ok := targetSpec.(string); ok { - if strings.HasSuffix(source, target) { - result = true + + if suffix, ok = args[ParamSuffix].(string); !ok { + return result, ErrWrongParamType(name, ParamSuffix, TypeString, args[ParamSuffix]) + } + if strings.HasPrefix(source, suffix) { + result = true + } else if v, exists := args[strParamOther]; exists { + argv := v.([]any) + for i, targetSpec := range argv { + if target, ok := targetSpec.(string); ok { + if strings.HasSuffix(source, target) { + result = true + break + } + } else { + err = fmt.Errorf("target item nr %d is %s, string expected", i+1, TypeName(targetSpec)) break } - } else { - err = fmt.Errorf("target item nr %d is %T, expected string", i+1, targetSpec) - break } } return } -func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err error) { +func splitStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) { var source, sep string var count int = -1 var parts []string var ok bool - if source, ok = args[0].(string); !ok { - return result, ErrWrongParamType(name, ParamSource, TypeString, args[0]) + if source, ok = args[ParamSource].(string); !ok { + return result, ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource]) } - if sep, ok = args[1].(string); !ok { - return nil, fmt.Errorf("separator param must be string, got %T (%v)", args[1], args[1]) + if sep, ok = args[ParamSeparator].(string); !ok { + return nil, fmt.Errorf("separator param must be string, got %s (%v)", TypeName(args[ParamSeparator]), args[ParamSeparator]) } - if count64, ok := args[2].(int64); ok { // TODO replace type assertion with toInt() + if count64, ok := args[ParamCount].(int64); ok { // TODO replace type assertion with toInt() count = int(count64) } else { - return nil, fmt.Errorf("part count must be integer, got %T (%v)", args[2], args[2]) + return nil, fmt.Errorf("part count must be integer, got %s (%v)", TypeName(args[ParamCount]), args[ParamCount]) } if count > 0 { @@ -182,37 +204,37 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err // Import above functions in the context func ImportStringFuncs(ctx ExprContext) { - ctx.RegisterFunc("strJoin", NewGolangFunctor(joinStrFunc), TypeString, []ExprFuncParam{ + ctx.RegisterFunc("strJoin", NewGolangFunctor2(joinStrFunc), TypeString, []ExprFuncParam{ NewFuncParam(ParamSeparator), NewFuncParamFlag(ParamItem, PfRepeat), }) - ctx.RegisterFunc("strSub", NewGolangFunctor(subStrFunc), TypeString, []ExprFuncParam{ + ctx.RegisterFunc("strSub", NewGolangFunctor2(subStrFunc), TypeString, []ExprFuncParam{ NewFuncParam(ParamSource), NewFuncParamFlagDef(ParamStart, PfDefault, int64(0)), NewFuncParamFlagDef(ParamCount, PfDefault, int64(-1)), }) - ctx.RegisterFunc("strSplit", NewGolangFunctor(splitStrFunc), "list of "+TypeString, []ExprFuncParam{ + ctx.RegisterFunc("strSplit", NewGolangFunctor2(splitStrFunc), "list of "+TypeString, []ExprFuncParam{ NewFuncParam(ParamSource), NewFuncParamFlagDef(ParamSeparator, PfDefault, ""), NewFuncParamFlagDef(ParamCount, PfDefault, int64(-1)), }) - ctx.RegisterFunc("strTrim", NewGolangFunctor(trimStrFunc), TypeString, []ExprFuncParam{ + ctx.RegisterFunc("strTrim", NewGolangFunctor2(trimStrFunc), TypeString, []ExprFuncParam{ NewFuncParam(ParamSource), }) - ctx.RegisterFunc("strStartsWith", NewGolangFunctor(startsWithStrFunc), TypeBoolean, []ExprFuncParam{ + ctx.RegisterFunc("strStartsWith", NewGolangFunctor2(startsWithStrFunc), TypeBoolean, []ExprFuncParam{ NewFuncParam(ParamSource), NewFuncParam(ParamPrefix), - NewFuncParamFlag("other "+ParamPrefix, PfRepeat), + NewFuncParamFlag(strParamOther, PfRepeat), }) - ctx.RegisterFunc("strEndsWith", NewGolangFunctor(endsWithStrFunc), TypeBoolean, []ExprFuncParam{ + ctx.RegisterFunc("strEndsWith", NewGolangFunctor2(endsWithStrFunc), TypeBoolean, []ExprFuncParam{ NewFuncParam(ParamSource), NewFuncParam(ParamSuffix), - NewFuncParamFlag("other "+ParamSuffix, PfRepeat), + NewFuncParamFlag(strParamOther, PfRepeat), }) } diff --git a/common-errors.go b/common-errors.go index 5b9d90b..5e11e92 100644 --- a/common-errors.go +++ b/common-errors.go @@ -6,8 +6,13 @@ package expr import ( "fmt" + "strings" ) +func ErrMissingParams(funcName string, missing []string) (err error) { + return fmt.Errorf("%s(): missing params -- %s", funcName, strings.Join(missing, ", ")) +} + func ErrTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error) { if maxArgs < 0 { err = fmt.Errorf("%s(): too few params -- expected %d or more, got %d", funcName, minArgs, argCount) @@ -17,8 +22,8 @@ func ErrTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error return } -func ErrTooMuchParams(funcName string, maxArgs, argCount int) (err error) { - err = fmt.Errorf("%s(): too much params -- expected %d, got %d", funcName, maxArgs, argCount) +func ErrTooManyParams(funcName string, maxArgs, argCount int) (err error) { + err = fmt.Errorf("%s(): too many params -- expected %d, got %d", funcName, maxArgs, argCount) return } diff --git a/common-params.go b/common-params.go index 8ce5d2b..38279f2 100644 --- a/common-params.go +++ b/common-params.go @@ -5,8 +5,10 @@ package expr const ( + ParamArgs = "args" ParamCount = "count" ParamItem = "item" + ParamIndex = "index" ParamParts = "parts" ParamSeparator = "separator" ParamSource = "source" @@ -19,6 +21,8 @@ const ( ParamEllipsis = "..." ParamFilepath = "filepath" ParamDirpath = "dirpath" + ParamHandle = "handle" + ParamResource = "resource" ParamIterator = "iterator" ) diff --git a/common-type-names.go b/common-type-names.go index a1b83d8..a1a9f42 100644 --- a/common-type-names.go +++ b/common-type-names.go @@ -5,16 +5,16 @@ package expr const ( - TypeAny = "any" - TypeBoolean = "boolean" - TypeFloat = "float" - TypeFraction = "fraction" - TypeHandle = "handle" - TypeInt = "integer" - TypeItem = "item" - TypeNumber = "number" - TypePair = "pair" - TypeString = "string" - TypeListOf = "list-of-" + TypeAny = "any" + TypeBoolean = "boolean" + TypeFloat = "float" + TypeFraction = "fraction" + TypeFileHandle = "file-handle" + TypeInt = "integer" + TypeItem = "item" + TypeNumber = "number" + TypePair = "pair" + TypeString = "string" + TypeListOf = "list-of-" TypeListOfStrings = "list-of-strings" ) diff --git a/data-cursor.go b/data-cursor.go index 8edcba3..ea72d66 100644 --- a/data-cursor.go +++ b/data-cursor.go @@ -95,7 +95,7 @@ func (dc *dataCursor) CallOperation(name string, args []any) (value any, err err value, err = dc.Reset() } else { ctx := cloneContext(dc.ctx) - value, err = functor.Invoke(ctx, name, []any{}) + value, err = functor.InvokeNamed(ctx, name, map[string]any{}) exportObjects(dc.ctx, ctx) } } else { @@ -108,7 +108,8 @@ func (dc *dataCursor) Reset() (success bool, err error) { if dc.resetFunc != nil { if dc.resource != nil { ctx := cloneContext(dc.ctx) - _, err = dc.resetFunc.Invoke(ctx, ResetName, []any{dc.resource}) + actualParams := buildActualParams(dc.resetFunc, []any{dc.resource}) + _, err = dc.resetFunc.InvokeNamed(ctx, ResetName, actualParams) exportObjects(dc.ctx, ctx) dc.index = -1 dc.count = 0 @@ -130,7 +131,8 @@ func (dc *dataCursor) Clean() (success bool, err error) { if dc.cleanFunc != nil { if dc.resource != nil { ctx := cloneContext(dc.ctx) - _, err = dc.cleanFunc.Invoke(ctx, CleanName, []any{dc.resource}) + actualParams := buildActualParams(dc.cleanFunc, []any{dc.resource}) + _, err = dc.cleanFunc.InvokeNamed(ctx, CleanName, actualParams) // dc.resource = nil exportObjects(dc.ctx, ctx) } else { @@ -158,7 +160,9 @@ func (dc *dataCursor) checkFilter(filter Functor, item any) (accepted bool, err var v any var ok bool ctx := cloneContext(dc.ctx) - if v, err = filter.Invoke(ctx, FilterName, []any{item, dc.index}); err == nil && v != nil { + + actualParams := buildActualParams(filter, []any{item, dc.index}) + if v, err = filter.InvokeNamed(ctx, FilterName, actualParams); err == nil && v != nil { if accepted, ok = v.(bool); !ok { accepted = true // NOTE: A non-boolean value that is not nil means the item has been accepted } @@ -168,7 +172,8 @@ func (dc *dataCursor) checkFilter(filter Functor, item any) (accepted bool, err func (dc *dataCursor) mapItem(mapper Functor, item any) (mappedItem any, err error) { ctx := cloneContext(dc.ctx) - mappedItem, err = mapper.Invoke(ctx, MapName, []any{item, dc.index}) + actualParams := buildActualParams(mapper, []any{item, dc.index}) + mappedItem, err = mapper.InvokeNamed(ctx, MapName, actualParams) return } @@ -193,7 +198,9 @@ func (dc *dataCursor) Next() (current any, err error) { // must return io.EOF af for item == nil && dc.lastErr == nil { ctx := cloneContext(dc.ctx) dc.index++ - if item, dc.lastErr = dc.nextFunc.Invoke(ctx, NextName, []any{dc.resource, dc.index}); dc.lastErr == nil { + + actualParams := buildActualParams(dc.nextFunc, []any{dc.resource, dc.index}) + if item, dc.lastErr = dc.nextFunc.InvokeNamed(ctx, NextName, actualParams); dc.lastErr == nil { if item == nil { dc.lastErr = io.EOF } else { @@ -231,3 +238,17 @@ func (dc *dataCursor) Index() int { func (dc *dataCursor) Count() int { return dc.count } + + +func buildActualParams(functor Functor, args []any) (actualParams map[string]any) { + formalParams := functor.GetParams() + actualParams = make(map[string]any, len(formalParams)) + for i, spec := range formalParams { + if i < len(formalParams) { + actualParams[spec.Name()] = args[i] + } else { + actualParams[spec.Name()] = nil + } + } + return +} diff --git a/expr-context.go b/expr-context.go index 53982bf..dae883a 100644 --- a/expr-context.go +++ b/expr-context.go @@ -7,7 +7,6 @@ package expr // ----Expression Context type ExprContext interface { Clone() ExprContext - // Merge(ctx ExprContext) SetParent(ctx ExprContext) GetParent() (ctx ExprContext) GetVar(varName string) (value any, exists bool) @@ -24,7 +23,7 @@ type ExprContext interface { DeleteFunc(funcName string) GetFuncInfo(name string) (item ExprFunc, exists bool) - Call(name string, args []any) (result any, err error) + Call(name string, args map[string]any) (result any, err error) RegisterFuncInfo(info ExprFunc) RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) error } diff --git a/expr-function.go b/expr-function.go index d8520ae..9a59731 100644 --- a/expr-function.go +++ b/expr-function.go @@ -7,7 +7,7 @@ package expr // ---- Functor interface type Functor interface { Typer - Invoke(ctx ExprContext, name string, args []any) (result any, err error) + InvokeNamed(ctx ExprContext, name string, args map[string]any) (result any, err error) SetFunc(info ExprFunc) GetFunc() ExprFunc GetParams() []ExprFuncParam @@ -33,6 +33,6 @@ type ExprFunc interface { Functor() Functor Params() []ExprFuncParam ReturnType() string - PrepareCall(parentCtx ExprContext, name string, varParams *[]any) (ctx ExprContext, err error) + PrepareCall(parentCtx ExprContext, name string, callTerm *term) (ctx ExprContext, actualParams map[string]any, err error) AllocContext(parentCtx ExprContext) (ctx ExprContext) } diff --git a/function.go b/function.go index 68a184f..3f8dbaf 100644 --- a/function.go +++ b/function.go @@ -10,7 +10,7 @@ import ( ) // ---- Function template -type FuncTemplate func(ctx ExprContext, name string, args []any) (result any, err error) +type FuncTemplate func(ctx ExprContext, name string, args map[string]any) (result any, err error) // ---- Common functor definition type baseFunctor struct { @@ -137,10 +137,6 @@ func newFuncInfo(name string, functor Functor, returnType string, params []ExprF return info, nil } -// func newUnnamedFuncInfo(functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) { -// return newFuncInfo("unnamed", functor, returnType, params) -// } - func (info *funcInfo) Params() []ExprFuncParam { return info.formalParams } @@ -216,21 +212,96 @@ func (info *funcInfo) AllocContext(parentCtx ExprContext) (ctx ExprContext) { return } -func (info *funcInfo) PrepareCall(parentCtx ExprContext, name string, varActualParams *[]any) (ctx ExprContext, err error) { - passedCount := len(*varActualParams) +func (info *funcInfo) checkExistingParam(paramName string) (exists bool) { + exists = false + for _, spec := range info.formalParams { + if spec.Name() == paramName { + exists = true + break + } + } + return +} + +func (info *funcInfo) initActualParams(ctx ExprContext, name string, callTerm *term) (params map[string]any, err error) { + var varArgs []any + var varName string + + namedParamsStarted := false + + actualParams := make(map[string]any, len(info.formalParams)) + for i, tree := range callTerm.children { + var paramValue any + paramCtx := ctx.Clone() + if paramValue, err = tree.compute(paramCtx); err != nil { + break + } + if paramName, namedParam := getAssignVarName(tree); namedParam { + if !info.checkExistingParam(paramName) { + err = fmt.Errorf("%s(): unknown param %q", name, paramName) + break + } + actualParams[paramName] = paramValue + namedParamsStarted = true + } else if !namedParamsStarted { + if varArgs != nil { + varArgs = append(varArgs, paramValue) + } else if i < len(info.formalParams) { + spec := info.formalParams[i] + if spec.IsRepeat() { + varArgs = make([]any, 0, len(callTerm.children)-i) + varArgs = append(varArgs, paramValue) + varName = spec.Name() + } else { + actualParams[spec.Name()] = paramValue + } + } else { + err = ErrTooManyParams(name, len(info.formalParams), len(callTerm.children)) + break + } + } else { + err = fmt.Errorf("%s(): positional param nr %d not allowed after named params", name, i+1) + break + } + } + if err == nil { + if varArgs != nil { + actualParams[varName] = varArgs + } + params = actualParams + } + return +} + +func (info *funcInfo) PrepareCall(parentCtx ExprContext, name string, callTerm *term) (ctx ExprContext, actualParams map[string]any, err error) { + if actualParams, err = info.initActualParams(parentCtx, name, callTerm); err != nil { + return + } + + passedCount := len(actualParams) if info.MinArgs() > passedCount { err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount) } - for i := passedCount; i < len(info.formalParams); i++ { - p := info.formalParams[i] - if !p.IsDefault() { - break + if passedCount < len(info.formalParams) { + for _, p := range info.formalParams { + if _, exists := actualParams[p.Name()]; !exists { + if !p.IsDefault() { + break + } + if p.IsRepeat() { + varArgs := make([]any, 1) + varArgs[0] = p.DefaultValue() + actualParams[p.Name()] = varArgs + } else { + actualParams[p.Name()] = p.DefaultValue() + } + } } - *varActualParams = append(*varActualParams, p.DefaultValue()) } - if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(*varActualParams) { - err = ErrTooMuchParams(name, info.MaxArgs(), len(*varActualParams)) + + if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(actualParams) { + err = ErrTooManyParams(name, info.MaxArgs(), len(actualParams)) } if err == nil { @@ -241,12 +312,20 @@ func (info *funcInfo) PrepareCall(parentCtx ExprContext, name string, varActualP // ----- Call a function --- -func CallFunction(parentCtx ExprContext, name string, actualParams []any) (result any, err error) { - if info, exists, _ := GetFuncInfo(parentCtx, name); exists { +func getAssignVarName(t *term) (name string, ok bool) { + if ok = t.symbol() == SymEqual; ok { + name = t.children[0].source() + } + return +} + +func CallFunction3(parentCtx ExprContext, name string, callTerm *term) (result any, err error) { + var actualParams map[string]any + if info, exists := GetFuncInfo(parentCtx, name); exists { var ctx ExprContext - if ctx, err = info.PrepareCall(parentCtx, name, &actualParams); err == nil { + if ctx, actualParams, err = info.PrepareCall(parentCtx, name, callTerm); err == nil { functor := info.Functor() - result, err = functor.Invoke(ctx, name, actualParams) + result, err = functor.InvokeNamed(ctx, name, actualParams) exportObjectsToParent(ctx) } } else { diff --git a/global-context.go b/global-context.go index 4ee6ed5..acab4af 100644 --- a/global-context.go +++ b/global-context.go @@ -55,7 +55,20 @@ func GetLocalFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool) } return } -func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool, ownerCtx ExprContext) { + +func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool) { + // if len(name) > 0 { + // if item, exists = GetLocalFuncInfo(ctx, name); exists { + // ownerCtx = ctx + // } else if item, exists = globalCtx.GetFuncInfo(name); exists { + // ownerCtx = globalCtx + // } + // } + item, exists, _ = GetFuncInfoAndOwner(ctx, name) + return +} + +func GetFuncInfoAndOwner(ctx ExprContext, name string) (item ExprFunc, exists bool, ownerCtx ExprContext) { if len(name) > 0 { if item, exists = GetLocalFuncInfo(ctx, name); exists { ownerCtx = ctx diff --git a/helpers.go b/helpers.go index f25fa10..6e16066 100644 --- a/helpers.go +++ b/helpers.go @@ -38,7 +38,7 @@ func EvalStringV(source string, args []Arg) (result any, err error) { for _, arg := range args { if isFunc(arg.Value) { if f, ok := arg.Value.(FuncTemplate); ok { - functor := NewGolangFunctor(f) + functor := NewGolangFunctor2(f) // ctx.RegisterFunc(arg.Name, functor, 0, -1) ctx.RegisterFunc(arg.Name, functor, TypeAny, []ExprFuncParam{ NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, 0), diff --git a/operand-func.go b/operand-func.go index 3ff3ba4..3e02347 100644 --- a/operand-func.go +++ b/operand-func.go @@ -21,20 +21,26 @@ func newFuncCallTerm(tk *Token, args []*term) *term { } // -------- eval func call +// func _evalFuncCall(ctx ExprContext, opTerm *term) (v any, err error) { +// name, _ := opTerm.tk.Value.(string) +// params := make([]any, len(opTerm.children), len(opTerm.children)+5) +// for i, tree := range opTerm.children { +// var param any +// if param, err = tree.compute(ctx); err != nil { +// break +// } +// params[i] = param +// } + +// if err == nil { +// v, err = CallFunction(ctx, name, params) +// } +// return +// } + func evalFuncCall(ctx ExprContext, opTerm *term) (v any, err error) { name, _ := opTerm.tk.Value.(string) - params := make([]any, len(opTerm.children), len(opTerm.children)+5) - for i, tree := range opTerm.children { - var param any - if param, err = tree.compute(ctx); err != nil { - break - } - params[i] = param - } - - if err == nil { - v, err = CallFunction(ctx, name, params) - } + v, err = CallFunction3(ctx, name, opTerm) return } diff --git a/operand-iterator.go b/operand-iterator.go index 369a438..beea309 100644 --- a/operand-iterator.go +++ b/operand-iterator.go @@ -11,22 +11,6 @@ import ( // -------- iterator term -// func newDsIteratorTerm(tk *Token, dsTerm *term, args []*term) *term { -// tk.Sym = SymIterator - -// children := make([]*term, 0, 1+len(args)) -// children = append(children, dsTerm) -// children = append(children, args...) -// return &term{ -// tk: *tk, -// parent: nil, -// children: children, -// position: posLeaf, -// priority: priValue, -// evalFunc: evalIterator, -// } -// } - func newIteratorTerm(tk *Token, args []*term) *term { tk.Sym = SymIterator return &term{ @@ -119,8 +103,10 @@ func evalIterator(ctx ExprContext, opTerm *term) (v any, err error) { args = []any{} } + actualParams := buildActualParams(initFunc, args) + initCtx := ctx.Clone() - if resource, err = initFunc.Invoke(initCtx, InitName, args); err != nil { + if resource, err = initFunc.InvokeNamed(initCtx, InitName, actualParams); err != nil { return } dcCtx := ctx.Clone() diff --git a/operand-var.go b/operand-var.go index 33a1fbc..590ab53 100644 --- a/operand-var.go +++ b/operand-var.go @@ -25,7 +25,7 @@ func evalVar(ctx ExprContext, opTerm *term) (v any, err error) { var exists bool name := opTerm.source() if v, exists = GetVar(ctx, name); !exists { - if info, exists, _ := GetFuncInfo(ctx, name); exists { + if info, exists := GetFuncInfo(ctx, name); exists { v = info.Functor() } else { err = fmt.Errorf("undefined variable or function %q", name) diff --git a/simple-store.go b/simple-store.go index b34f18b..2070f47 100644 --- a/simple-store.go +++ b/simple-store.go @@ -179,10 +179,10 @@ func (ctx *SimpleStore) DeleteFunc(funcName string) { delete(ctx.funcStore, funcName) } -func (ctx *SimpleStore) Call(name string, args []any) (result any, err error) { +func (ctx *SimpleStore) Call(name string, args map[string]any) (result any, err error) { if info, exists := GetLocalFuncInfo(ctx, name); exists { functor := info.Functor() - result, err = functor.Invoke(ctx, name, args) + result, err = functor.InvokeNamed(ctx, name, args) } else { err = fmt.Errorf("unknown function %s()", name) } diff --git a/t_builtin-base_test.go b/t_builtin-base_test.go index 2e757bf..fe17698 100644 --- a/t_builtin-base_test.go +++ b/t_builtin-base_test.go @@ -21,7 +21,7 @@ func TestFuncBase(t *testing.T) { /* 7 */ {`int(3.9)`, int64(3), nil}, /* 8 */ {`int("432")`, int64(432), nil}, /* 9 */ {`int("1.5")`, nil, `strconv.Atoi: parsing "1.5": invalid syntax`}, - /* 10 */ {`int("432", 4)`, nil, `int(): too much params -- expected 1, got 2`}, + /* 10 */ {`int("432", 4)`, nil, `int(): too many params -- expected 1, got 2`}, /* 11 */ {`int(nil)`, nil, `int(): can't convert nil to int`}, /* 12 */ {`isInt(2+1)`, true, nil}, /* 13 */ {`isInt(3.1)`, false, nil}, @@ -43,7 +43,7 @@ func TestFuncBase(t *testing.T) { /* 29 */ {`dec(true)`, float64(1), nil}, /* 30 */ {`dec(true")`, nil, `[1:11] missing string termination "`}, /* 31 */ {`dec()`, nil, `dec(): too few params -- expected 1, got 0`}, - /* 32 */ {`dec(1,2,3)`, nil, `dec(): too much params -- expected 1, got 3`}, + /* 32 */ {`dec(1,2,3)`, nil, `dec(): too many params -- expected 1, got 3`}, /* 33 */ {`isBool(false)`, true, nil}, /* 34 */ {`fract(1|2)`, newFraction(1, 2), nil}, /* 35 */ {`fract(12,2)`, newFraction(6, 1), nil}, @@ -61,5 +61,6 @@ func TestFuncBase(t *testing.T) { t.Setenv("EXPR_PATH", ".") + //runTestSuiteSpec(t, section, inputs, 10) runTestSuite(t, section, inputs) } diff --git a/t_builtin-fmt_test.go b/t_builtin-fmt_test.go index c664881..40e2bb2 100644 --- a/t_builtin-fmt_test.go +++ b/t_builtin-fmt_test.go @@ -30,7 +30,7 @@ func TestFmt(t *testing.T) { text := "ciao mondo" inputs := []inputType{ - /* 1 */ {fmt.Sprintf(`println("%s")`, text), int64(11), nil}, + /* 1 */ {fmt.Sprintf(`builtin "fmt"; println("%s")`, text), int64(11), nil}, } // t.Setenv("EXPR_PATH", ".") diff --git a/t_builtin-import_test.go b/t_builtin-import_test.go index 57626f5..a960ff4 100644 --- a/t_builtin-import_test.go +++ b/t_builtin-import_test.go @@ -19,6 +19,6 @@ func TestFuncImport(t *testing.T) { t.Setenv("EXPR_PATH", "test-resources") - // runTestSuiteSpec(t, section, inputs, 1) + //runTestSuiteSpec(t, section, inputs, 1) runTestSuite(t, section, inputs) } diff --git a/t_builtin-iterator_test.go b/t_builtin-iterator_test.go index 9448f92..25a2b67 100644 --- a/t_builtin-iterator_test.go +++ b/t_builtin-iterator_test.go @@ -17,12 +17,14 @@ func TestFuncRun(t *testing.T) { /* 3 */ {`builtin "iterator"; run($(1,2,3), func(index,item){status=status+item; true}, {"status":0})`, int64(6), nil}, /* 4 */ {`builtin ["iterator", "fmt"]; run($(1,2,3), func(index,item){println(item+10)})`, nil, nil}, /* 5 */ {`builtin "iterator"; run(nil)`, nil, `paramter "iterator" must be an iterator, passed [nil]`}, - /* 6 */ {`builtin "iterator"; run($(1,2,3), nil)`, nil, `paramter "operator" must be a function, passed [nil]`}, + /* 6 */ {`builtin "iterator"; run($(1,2,3), nil)`, nil, nil}, /* 7 */ {`builtin "iterator"; run($(1,2,3), func(){1}, "prrr")`, nil, `paramter "vars" must be a dictionary, passed prrr [string]`}, + /* 8 */ {`builtin "iterator"; run($(1,2,3), operator=nil)`, nil, nil}, + /* 9 */ {`builtin "iterator"; run($(1,2,3), operatorx=nil)`, nil, `run(): unknown param "operatorx"`}, } //t.Setenv("EXPR_PATH", ".") - //runTestSuiteSpec(t, section, inputs, 3) + //runTestSuiteSpec(t, section, inputs, 1) runTestSuite(t, section, inputs) } diff --git a/t_builtin-math-arith_test.go b/t_builtin-math-arith_test.go index 9240a78..cc7c5be 100644 --- a/t_builtin-math-arith_test.go +++ b/t_builtin-math-arith_test.go @@ -19,10 +19,15 @@ func TestFuncMathArith(t *testing.T) { /* 6 */ {`builtin "math.arith"; add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil}, /* 7 */ {`builtin "math.arith"; a=5; b=2; add(a, b*3)`, int64(11), nil}, /* 8 */ {`builtin "math.arith"; var2="abc"; add(1,2) but var2`, "abc", nil}, + /* 9 */ {`builtin "math.arith"; add()`, int64(0), nil}, + /* 10 */ {`builtin "math.arith"; mul()`, int64(1), nil}, + /* 11 */ {`builtin "math.arith"; add([1,2,3])`, int64(6), nil}, + /* 12 */ {`builtin "math.arith"; mul([2,2,3])`, int64(12), nil}, + /* 13 */ {`builtin "math.arith"; mul(2,2,3)`, int64(12), nil}, } // t.Setenv("EXPR_PATH", ".") - // runTestSuiteSpec(t, section, inputs, 1) + //runTestSuiteSpec(t, section, inputs, 10) runTestSuite(t, section, inputs) } diff --git a/t_builtin-os-file_test.go b/t_builtin-os-file_test.go index ad1875f..187cca5 100644 --- a/t_builtin-os-file_test.go +++ b/t_builtin-os-file_test.go @@ -30,6 +30,6 @@ func TestFuncOs(t *testing.T) { // t.Setenv("EXPR_PATH", ".") - // runTestSuiteSpec(t, section, inputs, 1) + //runTestSuiteSpec(t, section, inputs, 2) runTestSuite(t, section, inputs) } diff --git a/t_builtin-string_test.go b/t_builtin-string_test.go index be58d8a..a24acb1 100644 --- a/t_builtin-string_test.go +++ b/t_builtin-string_test.go @@ -63,6 +63,6 @@ func TestFuncString(t *testing.T) { //t.Setenv("EXPR_PATH", ".") - // runTestSuiteSpec(t, section, inputs, 19) + // runTestSuiteSpec(t, section, inputs, 16) runTestSuite(t, section, inputs) } diff --git a/t_common_test.go b/t_common_test.go index 23344e6..05bc66f 100644 --- a/t_common_test.go +++ b/t_common_test.go @@ -42,6 +42,8 @@ func runCtxTestSuite(t *testing.T, ctx ExprContext, section string, inputs []inp failed := 0 for i, input := range inputs { + // fmt.Printf("%3d: %s\n", i+1, input.source) + good := doTest(t, ctx, section, &input, i+1) if good { succeeded++ @@ -91,7 +93,7 @@ func doTest(t *testing.T, ctx ExprContext, section string, input *inputType, cou eq := reflect.DeepEqual(gotResult, input.wantResult) if !eq /*gotResult != input.wantResult*/ { - t.Errorf("%d: %q -> result = %v [%s], want = %v [%s]", count, input.source, gotResult, TypeName(gotResult), input.wantResult, TypeName(input.wantResult)) + t.Errorf("%d: `%s` -> result = %v [%s], want = %v [%s]", count, input.source, gotResult, TypeName(gotResult), input.wantResult, TypeName(input.wantResult)) good = false } @@ -106,8 +108,8 @@ func doTest(t *testing.T, ctx ExprContext, section string, input *inputType, cou func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) { if wantErr == nil { - t.Logf("[+]%s nr %3d -- %q --> %v", section, n, source, wantResult) + t.Logf("[+]%s nr %3d -- `%s` --> %v", section, n, source, wantResult) } else { - t.Logf("[-]%s nr %3d -- %q --> %v", section, n, source, wantErr) + t.Logf("[-]%s nr %3d -- `%s` --> %v", section, n, source, wantErr) } } diff --git a/t_expr_test.go b/t_expr_test.go index a920064..d285dc9 100644 --- a/t_expr_test.go +++ b/t_expr_test.go @@ -43,6 +43,6 @@ func TestExpr(t *testing.T) { } // t.Setenv("EXPR_PATH", ".") - //runTestSuiteSpec(t, section, inputs, 9) - runTestSuite(t, section, inputs) + runTestSuiteSpec(t, section, inputs, 17) + // runTestSuite(t, section, inputs) } diff --git a/t_funcs_test.go b/t_funcs_test.go index 0f27520..ff202a9 100644 --- a/t_funcs_test.go +++ b/t_funcs_test.go @@ -24,12 +24,17 @@ func TestFuncs(t *testing.T) { /* 11 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil}, /* 12 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil}, /* 13 */ {`two=func(){2}; four=func(f){f()+f()}; four(two)`, int64(4), nil}, - /* 14 */ {`two=func(){2}; two(123)`, nil, `two(): too much params -- expected 0, got 1`}, + /* 14 */ {`two=func(){2}; two(123)`, nil, `two(): too many params -- expected 0, got 1`}, /* 15 */ {`f=func(x,n=2){x+n}; f(3)`, int64(5), nil}, /* 16 */ {`f=func(x,n=2,y){x+n}`, nil, `[1:16] can't mix default and non-default parameters`}, /* 17 */ {`f=func(x,n){1}; f(3,4,)`, nil, "[1:24] expected `function-param-value`, got `)`"}, /* 18 */ {`factory=func(base){func(){@base=base+1}}; inc10=factory(10); inc5=factory(5); inc10(); inc5(); inc10()`, int64(12), nil}, /* 19 */ {`f=func(a,y=1,z="sos"){}; string(f)`, `f(a, y=1, z="sos"):any{}`, nil}, + /* 20 */ {`f=func(a,b){a*2+b}; f(1,10)`, int64(12), nil}, + /* 21 */ {`f=func(a,b){a*2+b}; f(a=2,b=1)`, int64(5), nil}, + /* 22 */ {`f=func(a,b){a*2+b}; f(b=2,a=1)`, int64(4), nil}, + /* 23 */ {`f=func(a=10,b=10){a*2+b}; f(b=1)`, int64(21), nil}, + /* 24 */ {`f=func(a,b){a*2+b}; f(a=1,2)`, nil, `f(): positional param nr 2 not allowed after named params`}, // /* 20 */ {`a=[func(){3}]; a[0]()`, int64(3), nil}, // /* 20 */ {`m={}; m["f"]=func(){3}; m["f"]()`, int64(3), nil}, // /* 18 */ {`f=func(a){a*2}`, nil, errors.New(`[1:24] expected "function-param-value", got ")"`)}, @@ -37,16 +42,16 @@ func TestFuncs(t *testing.T) { // t.Setenv("EXPR_PATH", ".") - //runTestSuiteSpec(t, section, inputs, 20) + //runTestSuiteSpec(t, section, inputs, 19) runTestSuite(t, section, inputs) } -func dummy(ctx ExprContext, name string, args []any) (result any, err error) { +func dummy(ctx ExprContext, name string, args map[string]any) (result any, err error) { return } func TestFunctionToStringSimple(t *testing.T) { - source := NewGolangFunctor(dummy) + source := NewGolangFunctor2(dummy) want := "func(){}" got := source.ToString(0) if got != want { @@ -56,7 +61,7 @@ func TestFunctionToStringSimple(t *testing.T) { func TestFunctionGetFunc(t *testing.T) { - source := NewGolangFunctor(dummy) + source := NewGolangFunctor2(dummy) want := ExprFunc(nil) got := source.GetFunc() if got != want { diff --git a/t_helpers_test.go b/t_helpers_test.go index 4c06ee0..7858266 100644 --- a/t_helpers_test.go +++ b/t_helpers_test.go @@ -5,50 +5,51 @@ package expr import ( - "fmt" "testing" ) -func subtract(ctx ExprContext, name string, args []any) (result any, err error) { - if len(args) != 2 { - err = fmt.Errorf("%s(): requires exactly two arguments", name) - return - } - x, xok := args[0].(int64) - y, yok := args[1].(int64) - if xok && yok { - result = x - y - } else { - err = fmt.Errorf("expected integer (int64) arguments, got %T and %T values", x, y) - } - return -} +// TODO The new function param model does not allow this kind of test +// ------------------------------------------------------------------ +// func subtract(ctx ExprContext, name string, args map[string]any) (result any, err error) { +// if len(args) != 2 { +// err = fmt.Errorf("%s(): requires exactly two arguments", name) +// return +// } +// x, xok := args[0].(int64) +// y, yok := args[1].(int64) +// if xok && yok { +// result = x - y +// } else { +// err = fmt.Errorf("expected integer (int64) arguments, got %T and %T values", x, y) +// } +// return +// } -func TestEvalStringA(t *testing.T) { +// func TestEvalStringA(t *testing.T) { - source := `a + b * subtract(4,2)` - args := []Arg{ - {"a", uint8(1)}, - {"b", int8(2)}, - {"subtract", FuncTemplate(subtract)}, - // force coverage - {"a16", uint16(1)}, - {"b16", int16(2)}, - {"a32", uint32(1)}, - {"b32", int32(2)}, - {"a64", uint64(1)}, - {"b64", int64(2)}, - {"f32", float32(1.0)}, - {"f64", float64(1.0)}, - } +// source := `a + b * subtract(4,2)` +// args := []Arg{ +// {"a", uint8(1)}, +// {"b", int8(2)}, +// {"subtract", FuncTemplate2(subtract)}, +// // force coverage +// {"a16", uint16(1)}, +// {"b16", int16(2)}, +// {"a32", uint32(1)}, +// {"b32", int32(2)}, +// {"a64", uint64(1)}, +// {"b64", int64(2)}, +// {"f32", float32(1.0)}, +// {"f64", float64(1.0)}, +// } - wantResult := int64(5) - gotResult, gotErr := EvalStringA(source, args...) - if value, ok := gotResult.(int64); ok && value != wantResult { - t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult) - t.Errorf("Error: %v", gotErr) - } -} +// wantResult := int64(5) +// gotResult, gotErr := EvalStringA(source, args...) +// if value, ok := gotResult.(int64); ok && value != wantResult { +// t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult) +// t.Errorf("Error: %v", gotErr) +// } +// } func TestEvalString(t *testing.T) { @@ -68,7 +69,7 @@ func TestEvalString(t *testing.T) { // force coverage ctx.GetFuncInfo("dummy") - ctx.Call("dummy", []any{}) + ctx.Call("dummy", map[string]any{}) source := `a + b * f` diff --git a/t_iterator_test.go b/t_iterator_test.go index 16116fa..238f301 100644 --- a/t_iterator_test.go +++ b/t_iterator_test.go @@ -27,6 +27,6 @@ func TestIteratorParser(t *testing.T) { /* 16 */ {`include "test-resources/filter.expr"; it=$(ds,10); it++`, int64(2), nil}, } - //runTestSuiteSpec(t, section, inputs, 3) + // runTestSuiteSpec(t, section, inputs, 7) runTestSuite(t, section, inputs) }