Compare commits
	
		
			14 Commits
		
	
	
		
			3b641ac793
			...
			fe5c8e9619
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| fe5c8e9619 | |||
| 7164e8816c | |||
| e0f3b028fc | |||
| 522b5983bd | |||
| f1e2163277 | |||
| bbdf498cf3 | |||
| f75c991ed2 | |||
| a7836143cc | |||
| ba9b9cb28f | |||
| ef1baa11a8 | |||
| cfddbd60b9 | |||
| 0760141caf | |||
| b9e780e659 | |||
| e41ddc664c | 
| @ -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, ¶ms); err == nil {
 | ||||
| // 			if v, err = ctx.Call(funcName, params); err == nil {
 | ||||
| // 				exportObjects(parentCtx, ctx)
 | ||||
| // 			}
 | ||||
| // 		}
 | ||||
| // 	}
 | ||||
| // 	return
 | ||||
| // }
 | ||||
|  | ||||
| @ -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), | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
| @ -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 | ||||
| } | ||||
|  | ||||
							
								
								
									
										13
									
								
								control.go
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								control.go
									
									
									
									
									
								
							| @ -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) | ||||
| } | ||||
|  | ||||
							
								
								
									
										81
									
								
								function.go
									
									
									
									
									
								
							
							
						
						
									
										81
									
								
								function.go
									
									
									
									
									
								
							| @ -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,12 +98,14 @@ func (param *funcParamInfo) DefaultValue() any { | ||||
| } | ||||
| 
 | ||||
| // --- Functions
 | ||||
| 
 | ||||
| // funcInfo implements ExprFunc
 | ||||
| type funcInfo struct { | ||||
| 	name         string | ||||
| 	minArgs      int | ||||
| 	maxArgs      int | ||||
| 	functor      Functor | ||||
| 	params     []ExprFuncParam | ||||
| 	formalParams []ExprFuncParam | ||||
| 	returnType   string | ||||
| } | ||||
| 
 | ||||
| @ -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 | ||||
| } | ||||
| 
 | ||||
| // ----- Call a function ---
 | ||||
| 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 checkFunctionCall(ctx ExprContext, name string, varParams *[]any) (err error) { | ||||
| 	if info, exists, owner := GetFuncInfo(ctx, name); exists { | ||||
| 		passedCount := len(*varParams) | ||||
| 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, p := range info.Params() { | ||||
| 			if i >= passedCount { | ||||
| 
 | ||||
| 	for i := passedCount; i < len(info.formalParams); i++ { | ||||
| 		p := info.formalParams[i] | ||||
| 		if !p.IsDefault() { | ||||
| 			break | ||||
| 		} | ||||
| 				*varParams = append(*varParams, 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(*varParams) { | ||||
| 			err = ErrTooMuchParams(name, info.MaxArgs(), len(*varParams)) | ||||
| 
 | ||||
| 	if err == nil { | ||||
| 		ctx = info.AllocContext(parentCtx) | ||||
| 	} | ||||
| 		if err == nil && owner != ctx { | ||||
| 			ctx.RegisterFuncInfo(info) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // ----- Call a function ---
 | ||||
| 
 | ||||
| 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, ¶ms); err == nil { | ||||
| 		result, err = ctx.Call(name, params) | ||||
| 		exportObjectsToParent(ctx) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| @ -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 | ||||
| 					} | ||||
|  | ||||
| @ -38,8 +38,6 @@ func evalNot(ctx ExprContext, self *term) (v any, err error) { | ||||
| func newAndTerm(tk *Token) (inst *term) { | ||||
| 	return &term{ | ||||
| 		tk:       *tk, | ||||
| 		// class:    classOperator,
 | ||||
| 		// kind:     kindBool,
 | ||||
| 		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 | ||||
| @ -107,8 +105,6 @@ func evalAndWithShortcut(ctx ExprContext, self *term) (v any, err error) { | ||||
| func newOrTerm(tk *Token) (inst *term) { | ||||
| 	return &term{ | ||||
| 		tk:       *tk, | ||||
| 		// class:    classOperator,
 | ||||
| 		// kind:     kindBool,
 | ||||
| 		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 | ||||
|  | ||||
| @ -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 | ||||
| 		} | ||||
|  | ||||
| @ -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) | ||||
| 		} | ||||
|  | ||||
| @ -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
									
								
							
							
						
						
									
										66
									
								
								t_bool_test.go
									
									
									
									
									
										Normal 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) | ||||
| } | ||||
| @ -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
									
								
							
							
						
						
									
										48
									
								
								t_builtin-fmt_test.go
									
									
									
									
									
										Normal 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") | ||||
| 	} | ||||
| } | ||||
| @ -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) | ||||
| } | ||||
|  | ||||
| @ -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) | ||||
| } | ||||
|  | ||||
| @ -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) | ||||
| } | ||||
|  | ||||
| @ -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
									
								
							
							
						
						
									
										113
									
								
								t_common_test.go
									
									
									
									
									
										Normal 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) | ||||
| 	} | ||||
| } | ||||
| @ -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) | ||||
| } | ||||
|  | ||||
| @ -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) { | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
| @ -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) | ||||
| } | ||||
|  | ||||
| @ -22,5 +22,5 @@ func TestIteratorParser(t *testing.T) { | ||||
| 	// inputs1 := []inputType{
 | ||||
| 	// 	/*   1 */ {`0?{}`, nil, nil},
 | ||||
| 	// }
 | ||||
| 	parserTest(t, "Iterator", inputs) | ||||
| 	runTestSuite(t, "Iterator", inputs) | ||||
| } | ||||
|  | ||||
| @ -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) | ||||
| } | ||||
|  | ||||
| @ -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
									
								
							
							
						
						
									
										31
									
								
								t_plugin_test.go
									
									
									
									
									
										Normal 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) | ||||
| 	} | ||||
| } | ||||
| @ -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) | ||||
| } | ||||
|  | ||||
| @ -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) | ||||
| } | ||||
|  | ||||
| @ -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) | ||||
| } | ||||
|  | ||||
| @ -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") | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								test-resources/expr-json-plugin.so
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test-resources/expr-json-plugin.so
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								test-resources/expr-json-plugin.so.debug
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test-resources/expr-json-plugin.so.debug
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										12
									
								
								utils.go
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								utils.go
									
									
									
									
									
								
							| @ -135,6 +135,7 @@ func anyInteger(v any) (i int64, ok bool) { | ||||
| } | ||||
| 
 | ||||
| func fromGenericAny(v any) (exprAny any, ok bool) { | ||||
| 	if v != nil { | ||||
| 		if exprAny, ok = v.(bool); ok { | ||||
| 			return | ||||
| 		} | ||||
| @ -153,6 +154,7 @@ func fromGenericAny(v any) (exprAny any, ok bool) { | ||||
| 		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 { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user