Compare commits

..

14 Commits

Author SHA1 Message Date
fe5c8e9619 tests improved 2024-06-26 04:29:40 +02:00
7164e8816c operator-bool.go: error messages improved 2024-06-26 04:28:53 +02:00
e0f3b028fc builtin-string.go: replaced ToInt() with ToGoInt() 2024-06-26 04:28:07 +02:00
522b5983bd builtin-base.go: string() function added 2024-06-26 04:27:14 +02:00
f1e2163277 replaced ToInt() with ToGoInt() 2024-06-26 04:25:49 +02:00
bbdf498cf3 function.go: commented out some unused error functions 2024-06-26 04:23:48 +02:00
f75c991ed2 common-errors.go: commented out some unused error functions 2024-06-26 04:23:14 +02:00
a7836143cc utils.go: ToInt() renamed as ToGoInt() 2024-06-26 04:22:29 +02:00
ba9b9cb28f a lot oh changes to the test framework and new test files t_builtin-fmt_test.go and t_plugin_test.go added 2024-06-25 10:59:03 +02:00
ef1baa11a8 builtin-fmt.go: print() and println() can write data to a generic Writer. They fetch the writer from the control variable '_stdout'.
If _stdout is nil, they write to os.Stdout
2024-06-25 10:55:54 +02:00
cfddbd60b9 control.go: use of UnsafeSetVar() in place of SetVar(). SetCtrl() added 2024-06-25 10:53:05 +02:00
0760141caf utils.go: Removed CloneMap(). fromGenericAny(): check v against nil 2024-06-25 10:48:31 +02:00
b9e780e659 ExprContext: removed Merge() method 2024-06-25 10:45:54 +02:00
e41ddc664c Improved closure context persistence. Now it is possibile to define counters like this func(base){func(){@base=base+1}} 2024-06-24 07:20:17 +02:00
36 changed files with 489 additions and 295 deletions

View File

@ -17,19 +17,31 @@ type exprFunctor struct {
// }
func newExprFunctor(e Expr, params []ExprFuncParam, ctx ExprContext) *exprFunctor {
return &exprFunctor{expr: e, params: params, defCtx: ctx}
var defCtx ExprContext
if ctx != nil {
// if ctx.GetParent() != nil {
// defCtx = ctx.Clone()
// defCtx.SetParent(ctx)
// } else {
defCtx = ctx
// }
}
return &exprFunctor{expr: e, params: params, defCtx: defCtx}
}
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)
}
// if functor.defCtx != nil {
// ctx.Merge(functor.defCtx)
// }
for i, p := range functor.params {
if i < len(args) {
arg := args[i]
if funcArg, ok := arg.(Functor); ok {
// ctx.RegisterFunc(p, functor, 0, -1)
paramSpecs := funcArg.GetParams()
ctx.RegisterFunc(p.Name(), funcArg, TypeAny, paramSpecs)
} else {
@ -42,17 +54,3 @@ func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (re
result, err = functor.expr.Eval(ctx)
return
}
// func CallExprFunction(parentCtx ExprContext, funcName string, params ...any) (v any, err error) {
// ctx := cloneContext(parentCtx)
// ctx.SetParent(parentCtx)
// if err == nil {
// if err = checkFunctionCall(ctx, funcName, &params); err == nil {
// if v, err = ctx.Call(funcName, params); err == nil {
// exportObjects(parentCtx, ctx)
// }
// }
// }
// return
// }

View File

@ -5,6 +5,7 @@
package expr
import (
"fmt"
"math"
"strconv"
)
@ -120,6 +121,32 @@ 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) {
case int64:
result = strconv.FormatInt(v, 10)
case float64:
result = strconv.FormatFloat(v, 'g', -1, 64)
case bool:
if v {
result = "true"
} else {
result = "false"
}
case string:
result = v
case *FractionType:
result = v.ToString(0)
case Formatter:
result = v.ToString(0)
case fmt.Stringer:
result = v.String()
default:
err = ErrCantConvert(name, v, "string")
}
return
}
func fractFunc(ctx ExprContext, name string, args []any) (result any, err error) {
switch v := args[0].(type) {
case int64:
@ -175,6 +202,7 @@ func ImportBuiltinsFuncs(ctx ExprContext) {
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{
NewFuncParam(ParamValue),
NewFuncParamFlagDef("denominator", PfDefault, 1),

View File

@ -4,11 +4,26 @@
// builtin-fmt.go
package expr
import "fmt"
import (
"fmt"
"io"
"os"
)
func getStdout(ctx ExprContext) io.Writer {
var w io.Writer
if wany, exists := ctx.GetVar(ControlStdout); exists && wany != nil {
w, _ = wany.(io.Writer)
}
if w == nil {
w = os.Stdout
}
return w
}
func printFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var n int
if n, err = fmt.Print(args...); err == nil {
if n, err = fmt.Fprint(getStdout(ctx), args...); err == nil {
result = int64(n)
}
return
@ -16,7 +31,7 @@ func printFunc(ctx ExprContext, name string, args []any) (result any, err error)
func printLnFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var n int
if n, err = fmt.Println(args...); err == nil {
if n, err = fmt.Fprintln(getStdout(ctx), args...); err == nil {
result = int64(n)
}
return

View File

@ -66,11 +66,11 @@ func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error
return nil, ErrWrongParamType(name, ParamSource, TypeString, args[0])
}
if start, err = ToInt(args[1], name+"()"); err != nil {
if start, err = ToGoInt(args[1], name+"()"); err != nil {
return
}
if count, err = ToInt(args[2], name+"()"); err != nil {
if count, err = ToGoInt(args[2], name+"()"); err != nil {
return
}

View File

@ -36,15 +36,15 @@ func ErrFuncDivisionByZero(funcName string) error {
return fmt.Errorf("%s(): division by zero", funcName)
}
func ErrDivisionByZero() error {
return fmt.Errorf("division by zero")
}
// func ErrDivisionByZero() error {
// return fmt.Errorf("division by zero")
// }
// --- Parameter errors
func ErrMissingRequiredParameter(funcName, paramName string) error {
return fmt.Errorf("%s(): missing required parameter %q", funcName, paramName)
}
// 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 %s (%v) for parameter %q", funcName, TypeName(paramValue), paramValue, paramName)

View File

@ -10,6 +10,7 @@ type Functor interface {
SetFunc(info ExprFunc)
GetFunc() ExprFunc
GetParams() []ExprFuncParam
GetDefinitionContext() ExprContext
}
// ---- Function Param Info
@ -31,12 +32,14 @@ type ExprFunc interface {
Functor() Functor
Params() []ExprFuncParam
ReturnType() string
PrepareCall(parentCtx ExprContext, name string, varParams *[]any) (ctx ExprContext, err error)
AllocContext(parentCtx ExprContext) (ctx ExprContext)
}
// ----Expression Context
type ExprContext interface {
Clone() ExprContext
Merge(ctx ExprContext)
// Merge(ctx ExprContext)
SetParent(ctx ExprContext)
GetParent() (ctx ExprContext)
GetVar(varName string) (value any, exists bool)
@ -47,7 +50,6 @@ type ExprContext interface {
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
GetFuncInfo(name string) (item ExprFunc, exists bool)
Call(name string, args []any) (result any, err error)
// RegisterFunc(name string, f Functor, minArgs, maxArgs int)
RegisterFuncInfo(info ExprFunc)
RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) error
}

View File

@ -11,6 +11,7 @@ const (
ControlBoolShortcut = "_bool_shortcut"
ControlSearchPath = "_search_path"
ControlParentContext = "_parent_context"
ControlStdout = "_stdout"
)
// Other control variables
@ -23,11 +24,17 @@ const (
init_search_path = "~/.local/lib/go-pkg/expr:/usr/local/lib/go-pkg/expr:/usr/lib/go-pkg/expr"
)
func SetCtrl(ctx ExprContext, name string, value any) (current any) {
current, _ = ctx.GetVar(name)
ctx.UnsafeSetVar(name, value)
return
}
func initDefaultVars(ctx ExprContext) {
if _, exists := ctx.GetVar(ControlPreset); exists {
return
}
ctx.SetVar(ControlPreset, true)
ctx.SetVar(ControlBoolShortcut, true)
ctx.SetVar(ControlSearchPath, init_search_path)
ctx.UnsafeSetVar(ControlPreset, true)
ctx.UnsafeSetVar(ControlBoolShortcut, true)
ctx.UnsafeSetVar(ControlSearchPath, init_search_path)
}

View File

@ -21,7 +21,7 @@ func (functor *baseFunctor) ToString(opt FmtOpt) (s string) {
if functor.info != nil {
s = functor.info.ToString(opt)
} else {
s = "func() {}"
s = "func(){}"
}
return s
}
@ -42,6 +42,10 @@ func (functor *baseFunctor) GetFunc() ExprFunc {
return functor.info
}
func (functor *baseFunctor) GetDefinitionContext() ExprContext {
return nil
}
// ---- Function Parameters
type paramFlags uint16
@ -94,13 +98,15 @@ func (param *funcParamInfo) DefaultValue() any {
}
// --- Functions
// funcInfo implements ExprFunc
type funcInfo struct {
name string
minArgs int
maxArgs int
functor Functor
params []ExprFuncParam
returnType string
name string
minArgs int
maxArgs int
functor Functor
formalParams []ExprFuncParam
returnType string
}
func newFuncInfo(name string, functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
@ -126,18 +132,18 @@ func newFuncInfo(name string, functor Functor, returnType string, params []ExprF
}
}
info = &funcInfo{
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, params: params,
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, formalParams: params,
}
functor.SetFunc(info)
return info, nil
}
func newUnnamedFuncInfo(functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
return newFuncInfo("unnamed", functor, returnType, params)
}
// 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.params
return info.formalParams
}
func (info *funcInfo) ReturnType() string {
@ -152,8 +158,8 @@ func (info *funcInfo) ToString(opt FmtOpt) string {
sb.WriteString(info.Name())
}
sb.WriteByte('(')
if info.params != nil {
for i, p := range info.params {
if info.formalParams != nil {
for i, p := range info.formalParams {
if i > 0 {
sb.WriteString(", ")
}
@ -180,7 +186,7 @@ func (info *funcInfo) ToString(opt FmtOpt) string {
} else {
sb.WriteString(TypeAny)
}
sb.WriteString(" {}")
sb.WriteString("{}")
return sb.String()
}
@ -200,41 +206,52 @@ func (info *funcInfo) Functor() Functor {
return info.functor
}
func (info *funcInfo) AllocContext(parentCtx ExprContext) (ctx ExprContext) {
if defCtx := info.functor.GetDefinitionContext(); defCtx != nil {
ctx = defCtx.Clone()
ctx.SetParent(defCtx)
} else {
ctx = parentCtx.Clone()
ctx.SetParent(parentCtx)
}
return
}
func (info *funcInfo) PrepareCall(parentCtx ExprContext, name string, varActualParams *[]any) (ctx ExprContext, err error) {
passedCount := len(*varActualParams)
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
}
*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 {
ctx = info.AllocContext(parentCtx)
}
return
}
// ----- Call a function ---
func checkFunctionCall(ctx ExprContext, name string, varParams *[]any) (err error) {
if info, exists, owner := GetFuncInfo(ctx, name); exists {
passedCount := len(*varParams)
if info.MinArgs() > passedCount {
err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
}
for i, p := range info.Params() {
if i >= passedCount {
if !p.IsDefault() {
break
}
*varParams = append(*varParams, p.DefaultValue())
}
}
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(*varParams) {
err = ErrTooMuchParams(name, info.MaxArgs(), len(*varParams))
}
if err == nil && owner != ctx {
ctx.RegisterFuncInfo(info)
func CallFunction(parentCtx ExprContext, name string, actualParams []any) (result any, err error) {
if info, exists, _ := GetFuncInfo(parentCtx, name); exists {
var ctx ExprContext
if ctx, err = info.PrepareCall(parentCtx, name, &actualParams); err == nil {
functor := info.Functor()
result, err = functor.Invoke(ctx, name, actualParams)
exportObjectsToParent(ctx)
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
func CallFunction(parentCtx ExprContext, name string, params []any) (result any, err error) {
ctx := cloneContext(parentCtx)
ctx.SetParent(parentCtx)
if err = checkFunctionCall(ctx, name, &params); err == nil {
result, err = ctx.Call(name, params)
exportObjectsToParent(ctx)
}
return
}

View File

@ -26,21 +26,21 @@ func NewListIterator(list *ListType, args []any) (it *ListIterator) {
}
it = &ListIterator{a: list, count: 0, index: -1, start: 0, stop: listLen - 1, step: 1}
if argc >= 1 {
if i, err := ToInt(args[0], "start index"); err == nil {
if i, err := ToGoInt(args[0], "start index"); err == nil {
if i < 0 {
i = listLen + i
}
it.start = i
}
if argc >= 2 {
if i, err := ToInt(args[1], "stop index"); err == nil {
if i, err := ToGoInt(args[1], "stop index"); err == nil {
if i < 0 {
i = listLen + i
}
it.stop = i
}
if argc >= 3 {
if i, err := ToInt(args[2], "step"); err == nil {
if i, err := ToGoInt(args[2], "step"); err == nil {
if i < 0 {
i = -i
}

View File

@ -37,9 +37,7 @@ func evalNot(ctx ExprContext, self *term) (v any, err error) {
func newAndTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
// class: classOperator,
// kind: kindBool,
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priAnd,
@ -88,7 +86,7 @@ func evalAndWithShortcut(ctx ExprContext, self *term) (v any, err error) {
}
if leftBool, lok := ToBool(leftValue); !lok {
err = fmt.Errorf("got %T as left operand type of 'and' operator, it must be bool", leftBool)
err = fmt.Errorf("got %s as left operand type of 'AND' operator, it must be bool", TypeName(leftValue))
return
} else if !leftBool {
v = false
@ -106,9 +104,7 @@ func evalAndWithShortcut(ctx ExprContext, self *term) (v any, err error) {
func newOrTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
// class: classOperator,
// kind: kindBool,
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priOr,
@ -157,7 +153,7 @@ func evalOrWithShortcut(ctx ExprContext, self *term) (v any, err error) {
}
if leftBool, lok := ToBool(leftValue); !lok {
err = fmt.Errorf("got %T as left operand type of 'or' operator, it must be bool", leftBool)
err = fmt.Errorf("got %s as left operand type of 'OR' operator, it must be bool", TypeName(leftValue))
return
} else if leftBool {
v = true

View File

@ -23,7 +23,7 @@ func verifyKey(indexTerm *term, indexList *ListType) (index any, err error) {
func verifyIndex(indexTerm *term, indexList *ListType, maxValue int) (index int, err error) {
var v int
if v, err = ToInt((*indexList)[0], "index expression"); err == nil {
if v, err = ToGoInt((*indexList)[0], "index expression"); err == nil {
if v < 0 && v >= -maxValue {
v = maxValue + v
}

View File

@ -36,7 +36,7 @@ func evalLength(ctx ExprContext, self *term) (v any, err error) {
} else if it, ok := childValue.(Iterator); ok {
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) {
count, _ := extIt.CallOperation(countName, nil)
v, _ = ToInt(count, "")
v, _ = ToGoInt(count, "")
} else {
v = int64(it.Index() + 1)
}

View File

@ -36,88 +36,21 @@ func (ctx *SimpleStore) GetParent() ExprContext {
}
func (ctx *SimpleStore) Clone() ExprContext {
return &SimpleStore{
clone := &SimpleStore{
varStore: CloneFilteredMap(ctx.varStore, filterRefName),
funcStore: CloneFilteredMap(ctx.funcStore, filterRefName),
}
return clone
}
func (ctx *SimpleStore) Merge(src ExprContext) {
for _, name := range src.EnumVars(filterRefName) {
ctx.varStore[name], _ = src.GetVar(name)
}
for _, name := range src.EnumFuncs(filterRefName) {
ctx.funcStore[name], _ = src.GetFuncInfo(name)
}
}
/*
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("vars: {\n")
first := true
for _, name := range ctx.EnumVars(nil) {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
value, _ := ctx.GetVar(name)
sb.WriteString(strings.Repeat("\t", indent+1))
sb.WriteString(name)
sb.WriteString(": ")
if f, ok := value.(Formatter); ok {
sb.WriteString(f.ToString(0))
} else 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))
}
}
sb.WriteString(strings.Repeat("\t", indent))
sb.WriteString("\n}")
}
func varsCtxToString(ctx ExprContext, indent int) string {
var sb strings.Builder
varsCtxToBuilder(&sb, ctx, indent)
return sb.String()
}
func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("funcs: {\n")
first := true
names := ctx.EnumFuncs(func(name string) bool { return true })
slices.Sort(names)
for _, name := range names {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
value, _ := ctx.GetFuncInfo(name)
sb.WriteString(strings.Repeat("\t", indent+1))
if formatter, ok := value.(Formatter); ok {
sb.WriteString(formatter.ToString(0))
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteString("\n}")
}
func (ctx *SimpleStore) ToString(opt FmtOpt) string {
var sb strings.Builder
varsCtxToBuilder(&sb, ctx, 0)
sb.WriteByte('\n')
funcsCtxToBuilder(&sb, ctx, 0)
return sb.String()
}
*/
// func (ctx *SimpleStore) Merge(src ExprContext) {
// for _, name := range src.EnumVars(filterRefName) {
// ctx.varStore[name], _ = src.GetVar(name)
// }
// for _, name := range src.EnumFuncs(filterRefName) {
// ctx.funcStore[name], _ = src.GetFuncInfo(name)
// }
// }
func (ctx *SimpleStore) ToString(opt FmtOpt) string {
dict := ctx.ToDict()

66
t_bool_test.go Normal file
View File

@ -0,0 +1,66 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_bool_test.go
package expr
import (
"errors"
"testing"
)
func TestBool(t *testing.T) {
section := "Bool"
inputs := []inputType{
/* 1 */ {`true`, true, nil},
/* 2 */ {`false`, false, nil},
/* 3 */ {`not false`, true, nil},
/* 4 */ {`not 1`, false, nil},
/* 5 */ {`not "true"`, false, nil},
/* 6 */ {`not "false"`, false, nil},
/* 7 */ {`not ""`, true, nil},
/* 8 */ {`not []`, nil, errors.New(`[1:4] prefix/postfix operator "NOT" do not support operand '[]' [list]`)},
/* 9 */ {`true and false`, false, nil},
/* 10 */ {`true and []`, nil, errors.New(`[1:9] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "AND"`)},
/* 11 */ {`[] and false`, nil, errors.New(`got list as left operand type of 'AND' operator, it must be bool`)},
/* 12 */ {`true or false`, true, nil},
/* 13 */ {`true or []`, true, nil},
/* 14 */ {`[] or false`, nil, errors.New(`got list as left operand type of 'OR' operator, it must be bool`)},
/* 13 */ //{`true or []`, nil, errors.New(`[1:8] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "OR"`)},
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 1)
runTestSuite(t, section, inputs)
}
func TestBoolNoShortcut(t *testing.T) {
section := "Bool-NoShortcut"
inputs := []inputType{
/* 1 */ {`true`, true, nil},
/* 2 */ {`false`, false, nil},
/* 3 */ {`not false`, true, nil},
/* 4 */ {`not 1`, false, nil},
/* 5 */ {`not "true"`, false, nil},
/* 6 */ {`not "false"`, false, nil},
/* 7 */ {`not ""`, true, nil},
/* 8 */ {`not []`, nil, errors.New(`[1:4] prefix/postfix operator "NOT" do not support operand '[]' [list]`)},
/* 9 */ {`true and false`, false, nil},
/* 10 */ {`true and []`, nil, errors.New(`[1:9] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "AND"`)},
/* 11 */ {`[] and false`, nil, errors.New(`[1:7] left operand '[]' [list] and right operand 'false' [bool] are not compatible with operator "AND"`)},
/* 12 */ {`true or false`, true, nil},
/* 13 */ {`true or []`, nil, errors.New(`[1:8] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "OR"`)},
/* 14 */ {`[] or false`, nil, errors.New(`[1:6] left operand '[]' [list] and right operand 'false' [bool] are not compatible with operator "OR"`)},
}
// t.Setenv("EXPR_PATH", ".")
ctx := NewSimpleStore()
current := SetCtrl(ctx, ControlBoolShortcut, false)
// runCtxTestSuiteSpec(t, ctx, section, inputs, 1)
runCtxTestSuite(t, ctx, section, inputs)
SetCtrl(ctx, ControlBoolShortcut, current)
}

View File

@ -63,5 +63,5 @@ func TestFuncBase(t *testing.T) {
t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 2)
parserTest(t, section, inputs)
runTestSuite(t, section, inputs)
}

48
t_builtin-fmt_test.go Normal file
View File

@ -0,0 +1,48 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_builtin-fmt.go
package expr
import (
// "errors"
"bytes"
"fmt"
"testing"
)
func TestFuncFmt(t *testing.T) {
section := "Builtin-Fmt"
inputs := []inputType{
/* 1 */ {`builtin "fmt"; print("ciao")`, int64(4), nil},
/* 2 */ {`builtin "fmt"; println(" ciao")`, int64(6), nil},
}
//t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 19)
runTestSuite(t, section, inputs)
}
func TestFmt(t *testing.T) {
section := "Builtin-Fmt"
text := "ciao mondo"
inputs := []inputType{
/* 1 */ {fmt.Sprintf(`println("%s")`, text), int64(11), nil},
}
// t.Setenv("EXPR_PATH", ".")
var b bytes.Buffer
ctx := NewSimpleStore()
currentStdout := SetCtrl(ctx, ControlStdout, &b)
runCtxTestSuite(t, ctx, section, inputs)
SetCtrl(ctx, ControlStdout, currentStdout)
if b.String() != text+"\n" {
t.Errorf("println(): Got: %q, Want: %q", b.String(), text+"\n")
}
}

View File

@ -20,5 +20,5 @@ func TestFuncImport(t *testing.T) {
t.Setenv("EXPR_PATH", "test-resources")
// parserTestSpec(t, section, inputs, 69)
parserTest(t, section, inputs)
runTestSuite(t, section, inputs)
}

View File

@ -25,5 +25,5 @@ func TestFuncMathArith(t *testing.T) {
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 69)
parserTest(t, section, inputs)
runTestSuite(t, section, inputs)
}

View File

@ -19,11 +19,18 @@ func TestFuncOs(t *testing.T) {
/* 5 */ {`builtin "os.file"; handle=fileAppend("/tmp/dummy"); fileWriteText(handle, "bye-bye"); fileClose(handle)`, true, nil},
/* 6 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); word=fileReadText(handle, "-"); fileClose(handle);word`, "bye", nil},
/* 7 */ {`builtin "os.file"; word=fileReadText(nil, "-")`, nil, errors.New(`fileReadText(): invalid file handle`)},
/* 7 */ {`builtin "os.file"; fileWriteText(nil, "bye")`, nil, errors.New(`fileWriteText(): invalid file handle`)},
/* 8 */ {`builtin "os.file"; fileWriteText(nil, "bye")`, nil, errors.New(`fileWriteText(): invalid file handle`)},
/* 9 */ {`builtin "os.file"; handle=fileOpen()`, nil, errors.New(`fileOpen(): too few params -- expected 1, got 0`)},
/* 10 */ {`builtin "os.file"; handle=fileOpen(123)`, nil, errors.New(`fileOpen(): missing or invalid file path`)},
/* 11 */ {`builtin "os.file"; handle=fileCreate(123)`, nil, errors.New(`fileCreate(): missing or invalid file path`)},
/* 12 */ {`builtin "os.file"; handle=fileAppend(123)`, nil, errors.New(`fileAppend(): missing or invalid file path`)},
/* 13 */ {`builtin "os.file"; handle=fileClose(123)`, nil, errors.New(`fileClose(): invalid file handle`)},
/* 14 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); c=fileReadTextAll(handle); fileClose(handle); c`, "bye-bye", nil},
/* 15 */ {`builtin "os.file"; c=fileReadTextAll(123)`, nil, errors.New(`fileReadTextAll(): invalid file handle 123 [int64]`)},
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 69)
parserTest(t, section, inputs)
runTestSuite(t, section, inputs)
}

View File

@ -64,6 +64,6 @@ func TestFuncString(t *testing.T) {
//t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 19)
parserTest(t, section, inputs)
// runTestSuiteSpec(t, section, inputs, 19)
runTestSuite(t, section, inputs)
}

113
t_common_test.go Normal file
View File

@ -0,0 +1,113 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_common_test.go
package expr
import (
"reflect"
"strings"
"testing"
)
type inputType struct {
source string
wantResult any
wantErr error
}
func runCtxTestSuiteSpec(t *testing.T, ctx ExprContext, section string, inputs []inputType, spec ...int) {
succeeded := 0
failed := 0
for _, count := range spec {
good := doTest(t, ctx, section, &inputs[count-1], count)
if good {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(spec), succeeded, failed)
}
func runTestSuiteSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
runCtxTestSuiteSpec(t, nil, section, inputs, spec...)
}
func runCtxTestSuite(t *testing.T, ctx ExprContext, section string, inputs []inputType) {
succeeded := 0
failed := 0
for i, input := range inputs {
good := doTest(t, ctx, section, &input, i+1)
if good {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}
func runTestSuite(t *testing.T, section string, inputs []inputType) {
runCtxTestSuite(t, nil, section, inputs)
/*
succeeded := 0
failed := 0
for i, input := range inputs {
good := doTest(t, nil, section, &input, i+1)
if good {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
*/
}
func doTest(t *testing.T, ctx ExprContext, section string, input *inputType, count int) (good bool) {
var expr Expr
var gotResult any
var gotErr error
parser := NewParser()
if ctx == nil {
ctx = NewSimpleStore()
}
logTest(t, count, section, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good = true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
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))
good = false
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> got-err = <%v>, expected-err = <%v>", count, input.source, gotErr, input.wantErr)
good = false
}
}
return
}
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)
} else {
t.Logf("[-]%s nr %3d -- %q --> %v", section, n, source, wantErr)
}
}

View File

@ -33,5 +33,5 @@ func TestExpr(t *testing.T) {
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 3)
parserTest(t, section, inputs)
runTestSuite(t, section, inputs)
}

View File

@ -34,7 +34,7 @@ func TestFractionsParser(t *testing.T) {
/* 20 */ {`fract("1a")`, (*FractionType)(nil), errors.New(`strconv.ParseInt: parsing "1a": invalid syntax`)},
/* 21 */ {`fract(1,0)`, nil, errors.New(`fract(): division by zero`)},
}
parserTest(t, section, inputs)
runTestSuite(t, section, inputs)
}
func TestFractionToStringSimple(t *testing.T) {

View File

@ -29,13 +29,15 @@ func TestFuncs(t *testing.T) {
/* 15 */ {`f=func(x,n=2){x+n}; f(3)`, int64(5), nil},
/* 16 */ {`f=func(x,n=2,y){x+n}`, nil, errors.New(`[1:16] can't mix default and non-default parameters`)},
/* 17 */ {`f=func(x,n){1}; f(3,4,)`, nil, errors.New(`[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},
// /* 18 */ {`f=func(a){a*2}`, nil, errors.New(`[1:24] expected "function-param-value", got ")"`)},
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 17)
parserTest(t, section, inputs)
// runTestSuiteSpec(t, section, inputs, 17)
runTestSuite(t, section, inputs)
}
func dummy(ctx ExprContext, name string, args []any) (result any, err error) {
@ -44,7 +46,7 @@ func dummy(ctx ExprContext, name string, args []any) (result any, err error) {
func TestFunctionToStringSimple(t *testing.T) {
source := NewGolangFunctor(dummy)
want := "func() {}"
want := "func(){}"
got := source.ToString(0)
if got != want {
t.Errorf(`(func() -> result = %v [%T], want = %v [%T]`, got, got, want, want)

View File

@ -23,5 +23,5 @@ func TestCollections(t *testing.T) {
t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 5)
parserTest(t, section, inputs)
runTestSuite(t, section, inputs)
}

View File

@ -22,5 +22,5 @@ func TestIteratorParser(t *testing.T) {
// inputs1 := []inputType{
// /* 1 */ {`0?{}`, nil, nil},
// }
parserTest(t, "Iterator", inputs)
runTestSuite(t, "Iterator", inputs)
}

View File

@ -58,5 +58,5 @@ func TestListParser(t *testing.T) {
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 17)
parserTest(t, section, inputs)
runTestSuite(t, section, inputs)
}

View File

@ -6,17 +6,9 @@ package expr
import (
"errors"
"reflect"
"strings"
"testing"
)
type inputType struct {
source string
wantResult any
wantErr error
}
func TestGeneralParser(t *testing.T) {
section := "Parser"
@ -148,78 +140,5 @@ func TestGeneralParser(t *testing.T) {
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 102)
parserTest(t, section, inputs)
}
func parserTestSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
succeeded := 0
failed := 0
for _, count := range spec {
good := doTest(t, section, &inputs[count-1], count)
if good {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(spec), succeeded, failed)
}
func parserTest(t *testing.T, section string, inputs []inputType) {
succeeded := 0
failed := 0
for i, input := range inputs {
good := doTest(t, section, &input, i+1)
if good {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}
func doTest(t *testing.T, section string, input *inputType, count int) (good bool) {
var expr Expr
var gotResult any
var gotErr error
ctx := NewSimpleStore()
parser := NewParser()
logTest(t, count, section, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good = true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
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))
good = false
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> got-err = <%v>, expected-err = <%v>", count, input.source, gotErr, input.wantErr)
good = false
}
}
return
}
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)
} else {
t.Logf("[-]%s nr %3d -- %q --> %v", section, n, source, wantErr)
}
runTestSuite(t, section, inputs)
}

31
t_plugin_test.go Normal file
View File

@ -0,0 +1,31 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_plugin_test.go
package expr
import (
"testing"
)
func _TestImportPlugin(t *testing.T) {
if err := importPlugin([]string{"test-resources"}, "json"); err != nil {
t.Errorf("importPlugin() failed: %v", err)
}
}
func TestPluginExists(t *testing.T) {
name := "json"
exists := pluginExists(name)
t.Logf("pluginExists(%v): %v", name, exists)
}
func TestMakePluginName(t *testing.T) {
name := "json"
want := "expr-" + name + "-plugin.so"
if got := makePluginName(name); got != want {
t.Errorf("makePluginName(%q) failed: Got: %q, Want: %q", name, got, want)
}
}

View File

@ -48,5 +48,5 @@ func TestRelational(t *testing.T) {
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 31)
parserTest(t, section, inputs)
runTestSuite(t, section, inputs)
}

View File

@ -17,5 +17,5 @@ func TestStringsParser(t *testing.T) {
/* 5 */ {`"abc"[1]`, `b`, nil},
/* 6 */ {`#"abc"`, int64(3), nil},
}
parserTest(t, "String", inputs)
runTestSuite(t, "String", inputs)
}

View File

@ -16,6 +16,6 @@ func TestSomething(t *testing.T) {
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 1)
parserTest(t, section, inputs)
// runTestSuiteSpec(t, section, inputs, 1)
runTestSuite(t, section, inputs)
}

View File

@ -6,6 +6,7 @@ package expr
import (
"errors"
"reflect"
"testing"
)
@ -97,7 +98,7 @@ func TestToIntOk(t *testing.T) {
wantValue := int(64)
wantErr := error(nil)
gotValue, gotErr := ToInt(source, "test")
gotValue, gotErr := ToGoInt(source, "test")
if gotErr != nil || gotValue != wantValue {
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
@ -110,7 +111,7 @@ func TestToIntErr(t *testing.T) {
wantValue := 0
wantErr := errors.New(`test expected integer, got uint64 (64)`)
gotValue, gotErr := ToInt(source, "test")
gotValue, gotErr := ToGoInt(source, "test")
if gotErr.Error() != wantErr.Error() || gotValue != wantValue {
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
@ -154,3 +155,12 @@ func TestAnyInteger(t *testing.T) {
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}
func TestCopyMap(t *testing.T) {
source := map[string]int{"one": 1, "two": 2, "three": 3}
dest := make(map[string]int)
result := CopyMap(dest, source)
if !reflect.DeepEqual(result, source) {
t.Errorf("utils.CopyMap() failed")
}
}

Binary file not shown.

Binary file not shown.

View File

@ -135,23 +135,25 @@ func anyInteger(v any) (i int64, ok bool) {
}
func fromGenericAny(v any) (exprAny any, ok bool) {
if exprAny, ok = v.(bool); ok {
return
}
if exprAny, ok = v.(string); ok {
return
}
if exprAny, ok = anyInteger(v); ok {
return
}
if exprAny, ok = anyFloat(v); ok {
return
}
if exprAny, ok = v.(*DictType); ok {
return
}
if exprAny, ok = v.(*ListType); ok {
return
if v != nil {
if exprAny, ok = v.(bool); ok {
return
}
if exprAny, ok = v.(string); ok {
return
}
if exprAny, ok = anyInteger(v); ok {
return
}
if exprAny, ok = anyFloat(v); ok {
return
}
if exprAny, ok = v.(*DictType); ok {
return
}
if exprAny, ok = v.(*ListType); ok {
return
}
}
return
}
@ -176,10 +178,10 @@ func CopyMap[K comparable, V any](dest, source map[K]V) map[K]V {
return dest
}
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 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)
@ -201,7 +203,7 @@ func CloneFilteredMap[K comparable, V any](source map[K]V, filter func(key K) (a
return CopyFilteredMap(dest, source, filter)
}
func ToInt(value any, description string) (i int, err error) {
func ToGoInt(value any, description string) (i int, err error) {
if valueInt64, ok := value.(int64); ok {
i = int(valueInt64)
} else if valueInt, ok := value.(int); ok {