New interface and implementation to model function parameters

This commit is contained in:
Celestino Amoroso 2024-05-22 20:52:44 +02:00
parent ba32e2dccf
commit 1ff5770264
19 changed files with 405 additions and 142 deletions

View File

@ -5,7 +5,26 @@
package expr
const (
paramCount = "count"
paramItem = "item"
paramParts = "parts"
paramSeparator = "separator"
paramSource = "source"
paramSuffix = "suffix"
paramPrefix = "prefix"
paramStart = "start"
paramEnd = "end"
paramValue = "value"
paramEllipsis = "..."
typeFilepath = "filepath"
typeDirpath = "dirpath"
)
// const (
// typeInteger = "int"
// typeFloat = "float"
// typeString = "string"
// typeFraction = "fract"
// typeList = "list"
// typeDict = "dict"
// )

View File

@ -5,10 +5,13 @@
package expr
const (
typeAny = "any"
typeBoolean = "boolean"
typeFloat = "decimal"
typeFraction = "fraction"
typeHandle = "handle"
typeInt = "integer"
typeItem = "item"
typeNumber = "number"
typeString = "string"
)

View File

@ -22,7 +22,9 @@ func exportFunc(ctx ExprContext, name string, info ExprFunc) {
if name[0] == '@' {
name = name[1:]
}
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
// ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
// ctx.RegisterFuncInfo(name, info)
ctx.RegisterFunc2(name, info.Functor(), info.ReturnType(), info.Params())
}
func exportObjects(destCtx, sourceCtx ExprContext) {

View File

@ -16,16 +16,35 @@ type simpleFunctor struct {
f FuncTemplate
}
func newSimpleFunctor(f FuncTemplate) *simpleFunctor {
return &simpleFunctor{f: f}
}
func (functor *simpleFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
return functor.f(ctx, name, args)
}
func (functor *simpleFunctor) ToString(opt FmtOpt) string {
return "func() {<body>}"
}
// ---- Function Param Info
type ExprFuncParam interface {
Name() string
Type() string
IsOptional() bool
IsRepeat() bool
DefaultValue() any
}
// ---- Function Info
type ExprFunc interface {
Name() string
MinArgs() int
MaxArgs() int
Functor() Functor
Params() []ExprFuncParam
ReturnType() string
}
// ----Expression Context
@ -38,5 +57,7 @@ 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)
// RegisterFunc(name string, f Functor, minArgs, maxArgs int)
RegisterFuncInfo(info ExprFunc)
RegisterFunc2(name string, f Functor, returnType string, param []ExprFuncParam) error
}

View File

@ -119,10 +119,6 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
}
case float64:
result, err = float64ToFraction(v)
// var n, d int64
// if n, d, err = float64ToFraction(v); err == nil {
// result = newFraction(n, d)
// }
case bool:
if v {
result = newFraction(1, 1)
@ -131,16 +127,6 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
}
case string:
result, err = makeGeneratingFraction(v)
// var f float64
// // TODO temporary implementation
// if f, err = strconv.ParseFloat(v, 64); err == nil {
// var n, d int64
// if n, d, err = float64ToFraction(f); err == nil {
// result = newFraction(n, d)
// }
// } else {
// errors.New("convertion from string to float is ongoing")
// }
case *fraction:
result = v
default:
@ -154,20 +140,41 @@ func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err err
}
func ImportBuiltinsFuncs(ctx ExprContext) {
ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, 1)
ctx.RegisterFunc("isInt", &simpleFunctor{f: isIntFunc}, 1, 1)
ctx.RegisterFunc("isFloat", &simpleFunctor{f: isFloatFunc}, 1, 1)
ctx.RegisterFunc("isBool", &simpleFunctor{f: isBoolFunc}, 1, 1)
ctx.RegisterFunc("isString", &simpleFunctor{f: isStringFunc}, 1, 1)
ctx.RegisterFunc("isFraction", &simpleFunctor{f: isFractionFunc}, 1, 1)
ctx.RegisterFunc("isFract", &simpleFunctor{f: isFractionFunc}, 1, 1)
ctx.RegisterFunc("isRational", &simpleFunctor{f: isRationalFunc}, 1, 1)
ctx.RegisterFunc("isList", &simpleFunctor{f: isListFunc}, 1, 1)
ctx.RegisterFunc("isDictionary", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
ctx.RegisterFunc("isDict", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, 1)
ctx.RegisterFunc("dec", &simpleFunctor{f: decFunc}, 1, 1)
ctx.RegisterFunc("fract", &simpleFunctor{f: fractFunc}, 1, 2)
anyParams := []ExprFuncParam{
newFuncParam(paramValue),
}
// ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, 1)
ctx.RegisterFunc2("isNil", newSimpleFunctor(isNilFunc), typeBoolean, anyParams)
// ctx.RegisterFunc("isInt", &simpleFunctor{f: isIntFunc}, 1, 1)
ctx.RegisterFunc2("isInt", newSimpleFunctor(isIntFunc), typeBoolean, anyParams)
// ctx.RegisterFunc("isFloat", &simpleFunctor{f: isFloatFunc}, 1, 1)
ctx.RegisterFunc2("isFloat", newSimpleFunctor(isFloatFunc), typeBoolean, anyParams)
// ctx.RegisterFunc("isBool", &simpleFunctor{f: isBoolFunc}, 1, 1)
ctx.RegisterFunc2("isBool", newSimpleFunctor(isBoolFunc), typeBoolean, anyParams)
// ctx.RegisterFunc("isString", &simpleFunctor{f: isStringFunc}, 1, 1)
ctx.RegisterFunc2("isString", newSimpleFunctor(isStringFunc), typeBoolean, anyParams)
// ctx.RegisterFunc("isFraction", &simpleFunctor{f: isFractionFunc}, 1, 1)
//ctx.RegisterFunc2("isFraction", &simpleFunctor{f: isFractionFunc}, typeBoolean, anyParams)
// ctx.RegisterFunc("isFract", &simpleFunctor{f: isFractionFunc}, 1, 1)
ctx.RegisterFunc2("isFract", newSimpleFunctor(isFractionFunc), typeBoolean, anyParams)
// ctx.RegisterFunc("isRational", &simpleFunctor{f: isRationalFunc}, 1, 1)
ctx.RegisterFunc2("isRational", newSimpleFunctor(isRationalFunc), typeBoolean, anyParams)
// ctx.RegisterFunc("isList", &simpleFunctor{f: isListFunc}, 1, 1)
ctx.RegisterFunc2("isList", newSimpleFunctor(isListFunc), typeBoolean, anyParams)
// ctx.RegisterFunc("isDictionary", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
//ctx.RegisterFunc2("isDictionary", &simpleFunctor{f: isDictionaryFunc}, typeBoolean, anyParams)
// ctx.RegisterFunc("isDict", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
ctx.RegisterFunc2("isDict", newSimpleFunctor(isDictionaryFunc), typeBoolean, anyParams)
//ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, 1)
ctx.RegisterFunc2("int", newSimpleFunctor(intFunc), typeInt, anyParams)
// ctx.RegisterFunc("dec", &simpleFunctor{f: decFunc}, 1, 1)
ctx.RegisterFunc2("dec", newSimpleFunctor(decFunc), typeFloat, anyParams)
// ctx.RegisterFunc("fract", &simpleFunctor{f: fractFunc}, 1, 2)
ctx.RegisterFunc2("fract", newSimpleFunctor(fractFunc), typeFraction, []ExprFuncParam{
newFuncParam(paramValue),
newFuncParamFlagDef("denominator", pfOptional, 1),
})
}
func init() {

View File

@ -137,8 +137,14 @@ func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (resu
}
func ImportImportFuncs(ctx ExprContext) {
ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1)
ctx.RegisterFunc("importAll", &simpleFunctor{f: importAllFunc}, 1, -1)
// ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1)
ctx.RegisterFunc2("import", newSimpleFunctor(importFunc), typeAny, []ExprFuncParam{
newFuncParamFlag(typeFilepath, pfRepeat),
})
//ctx.RegisterFunc("importAll", &simpleFunctor{f: importAllFunc}, 1, -1)
ctx.RegisterFunc2("importAll", newSimpleFunctor(importAllFunc), typeAny, []ExprFuncParam{
newFuncParamFlag(typeFilepath, pfRepeat),
})
}
func init() {

View File

@ -167,8 +167,15 @@ func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
}
func ImportMathFuncs(ctx ExprContext) {
ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
//ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
ctx.RegisterFunc2("add", &simpleFunctor{f: addFunc}, typeNumber, []ExprFuncParam{
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 0),
})
// ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
ctx.RegisterFunc2("mul", &simpleFunctor{f: mulFunc}, typeNumber, []ExprFuncParam{
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 1),
})
}
func init() {

View File

@ -158,12 +158,32 @@ func readFileFunc(ctx ExprContext, name string, args []any) (result any, err err
}
func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("openFile", &simpleFunctor{f: openFileFunc}, 1, 1)
ctx.RegisterFunc("appendFile", &simpleFunctor{f: appendFileFunc}, 1, 1)
ctx.RegisterFunc("createFile", &simpleFunctor{f: createFileFunc}, 1, 1)
ctx.RegisterFunc("writeFile", &simpleFunctor{f: writeFileFunc}, 1, -1)
ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2)
ctx.RegisterFunc("closeFile", &simpleFunctor{f: closeFileFunc}, 1, 1)
// ctx.RegisterFunc("openFile", &simpleFunctor{f: openFileFunc}, 1, 1)
ctx.RegisterFunc2("openFile", newSimpleFunctor(openFileFunc), typeHandle, []ExprFuncParam{
newFuncParam(typeFilepath),
})
// ctx.RegisterFunc("appendFile", &simpleFunctor{f: appendFileFunc}, 1, 1)
ctx.RegisterFunc2("appendFile", newSimpleFunctor(appendFileFunc), typeHandle, []ExprFuncParam{
newFuncParam(typeFilepath),
})
// ctx.RegisterFunc("createFile", &simpleFunctor{f: createFileFunc}, 1, 1)
ctx.RegisterFunc2("createFile", newSimpleFunctor(createFileFunc), typeHandle, []ExprFuncParam{
newFuncParam(typeFilepath),
})
// ctx.RegisterFunc("writeFile", &simpleFunctor{f: writeFileFunc}, 1, -1)
ctx.RegisterFunc2("writeFile", newSimpleFunctor(writeFileFunc), typeInt, []ExprFuncParam{
newFuncParam(typeHandle),
newFuncParamFlagDef(typeItem, pfOptional|pfRepeat, ""),
})
// ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2)
ctx.RegisterFunc2("readFile", newSimpleFunctor(readFileFunc), typeString, []ExprFuncParam{
newFuncParam(typeHandle),
newFuncParamFlagDef("limitCh", pfOptional, "\\n"),
})
//ctx.RegisterFunc("closeFile", &simpleFunctor{f: closeFileFunc}, 1, 1)
ctx.RegisterFunc2("closeFile", newSimpleFunctor(closeFileFunc), typeBoolean, []ExprFuncParam{
newFuncParam(typeHandle),
})
}
func init() {

View File

@ -62,9 +62,6 @@ func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error
var source string
var ok bool
// if len(args) < 1 {
// return nil, errMissingRequiredParameter(name, paramSource)
// }
if source, ok = args[0].(string); !ok {
return nil, errWrongParamType(name, paramSource, typeString, args[0])
}
@ -93,9 +90,6 @@ func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err erro
var source string
var ok bool
// if len(args) < 1 {
// return nil, errMissingRequiredParameter(name, paramSource)
// }
if source, ok = args[0].(string); !ok {
return nil, errWrongParamType(name, paramSource, typeString, args[0])
}
@ -108,9 +102,7 @@ func startsWithStrFunc(ctx ExprContext, name string, args []any) (result any, er
var ok bool
result = false
// if len(args) < 1 {
// return result, errMissingRequiredParameter(name, paramSource)
// }
if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0])
}
@ -133,9 +125,7 @@ func endsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err
var ok bool
result = false
// if len(args) < 1 {
// return result, errMissingRequiredParameter(name, paramSource)
// }
if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0])
}
@ -159,9 +149,6 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
var parts []string
var ok bool
// if len(args) < 1 {
// return result, errMissingRequiredParameter(name, paramSource)
// }
if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0])
}
@ -196,12 +183,36 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
// Import above functions in the context
func ImportStringFuncs(ctx ExprContext) {
ctx.RegisterFunc("joinStr", &simpleFunctor{f: joinStrFunc}, 1, -1)
ctx.RegisterFunc("subStr", &simpleFunctor{f: subStrFunc}, 1, -1)
ctx.RegisterFunc("splitStr", &simpleFunctor{f: splitStrFunc}, 2, -1)
ctx.RegisterFunc("trimStr", &simpleFunctor{f: trimStrFunc}, 1, -1)
ctx.RegisterFunc("startsWithStr", &simpleFunctor{f: startsWithStrFunc}, 2, -1)
ctx.RegisterFunc("endsWithStr", &simpleFunctor{f: endsWithStrFunc}, 2, -1)
ctx.RegisterFunc2("joinStr", newSimpleFunctor(joinStrFunc), typeString, []ExprFuncParam{
newFuncParam(paramSeparator),
newFuncParamFlagDef(paramItem, pfOptional|pfRepeat, ""),
})
ctx.RegisterFunc2("subStr", newSimpleFunctor(subStrFunc), typeString, []ExprFuncParam{
newFuncParam(paramSource),
newFuncParamFlagDef(paramStart, pfOptional, 0),
newFuncParamFlagDef(paramCount, pfOptional, -1),
})
ctx.RegisterFunc2("splitStr", newSimpleFunctor(splitStrFunc), "list of "+typeString, []ExprFuncParam{
newFuncParam(paramSource),
newFuncParamFlagDef(paramSeparator, pfOptional, ""),
newFuncParamFlagDef(paramCount, pfOptional, -1),
})
ctx.RegisterFunc2("trimStr", newSimpleFunctor(trimStrFunc), typeString, []ExprFuncParam{
newFuncParam(paramSource),
})
ctx.RegisterFunc2("startsWithStr", newSimpleFunctor(startsWithStrFunc), typeBoolean, []ExprFuncParam{
newFuncParam(paramSource),
newFuncParamFlag(paramPrefix, pfRepeat),
})
ctx.RegisterFunc2("endsWithStr", newSimpleFunctor(endsWithStrFunc), typeBoolean, []ExprFuncParam{
newFuncParam(paramSource),
newFuncParamFlag(paramSuffix, pfRepeat),
})
}
// Register the import function in the import-register.

View File

@ -76,14 +76,41 @@ func TestFuncs(t *testing.T) {
/* 63 */ {`dec(true)`, float64(1), nil},
/* 64 */ {`dec(true")`, nil, errors.New("[1:11] missing string termination \"")},
/* 65 */ {`builtin "string"; joinStr("-", [1, "two", "three"])`, nil, errors.New(`joinStr() expected string, got int64 (1)`)},
/* 65 */ {`dec()`, nil, errors.New(`too few params -- expected 1, got 0`)},
/* 66 */ {`dec(1,2,3)`, nil, errors.New(`too much params -- expected 1, got 3`)},
/* 67 */ {`builtin "string"; joinStr()`, nil, errors.New(`too few params -- expected 1 or more, got 0`)},
// /* 64 */ {`string(true)`, "true", nil},
/* 66 */ {`dec()`, nil, errors.New(`too few params -- expected 1, got 0`)},
/* 67 */ {`dec(1,2,3)`, nil, errors.New(`too much params -- expected 1, got 3`)},
/* 68 */ {`builtin "string"; joinStr()`, nil, errors.New(`too few params -- expected 1 or more, got 0`)},
/* 69 */ /*{`builtin "string"; $$global`, `vars: {
}
funcs: {
add(any=0 ...) -> number,
dec(any) -> decimal,
endsWithStr(source, suffix) -> boolean,
fract(any, denominator=1) -> fraction,
import( ...) -> any,
importAll( ...) -> any,
int(any) -> integer,
isBool(any) -> boolean,
isDec(any) -> boolean,
isDict(any) -> boolean,
isFloat(any) -> boolean,
isFract(any) -> boolean,
isInt(any) -> boolean,
isList(any) -> boolean,
isNil(any) -> boolean,
isString(any) -> boolean,
joinStr(separator, item="" ...) -> string,
mul(any=1 ...) -> number,
splitStr(source, separator="", count=-1) -> list of string,
startsWithStr(source, prefix) -> boolean,
subStr(source, start=0, count=-1) -> string,
trimStr(source) -> string
}
`, nil},*/
}
t.Setenv("EXPR_PATH", ".")
//parserTest(t, "Func", inputs[54:55])
// parserTestSpec(t, "Func", inputs, 69)
parserTest(t, "Func", inputs)
}

View File

@ -51,4 +51,5 @@ func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool, owne
func init() {
globalCtx = NewSimpleFuncStore()
ImportBuiltinsFuncs(globalCtx)
}

View File

@ -38,8 +38,11 @@ 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 := &simpleFunctor{f: f}
ctx.RegisterFunc(arg.Name, functor, 0, -1)
functor := newSimpleFunctor(f)
// ctx.RegisterFunc(arg.Name, functor, 0, -1)
ctx.RegisterFunc2(arg.Name, functor, typeAny, []ExprFuncParam{
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 0),
})
} else {
err = fmt.Errorf("invalid function specification: %q", arg.Name)
}

View File

@ -31,7 +31,8 @@ func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) {
err = errTooMuchParams(info.MaxArgs(), len(params))
}
if err == nil && owner != ctx {
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
// ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
ctx.RegisterFuncInfo(info)
}
} else {
err = fmt.Errorf("unknown function %s()", name)
@ -85,7 +86,10 @@ func (functor *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any)
if i < len(args) {
arg := args[i]
if functor, ok := arg.(Functor); ok {
ctx.RegisterFunc(p, functor, 0, -1)
// ctx.RegisterFunc(p, functor, 0, -1)
ctx.RegisterFunc2(p, functor, typeAny, []ExprFuncParam{
newFuncParam(paramValue),
})
} else {
ctx.UnsafeSetVar(p, arg)
}

View File

@ -31,18 +31,20 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
if v, err = rightChild.compute(ctx); err == nil {
if functor, ok := v.(Functor); ok {
var minArgs, maxArgs int = 0, -1
funcName := rightChild.source()
if info, exists := ctx.GetFuncInfo(funcName); exists {
minArgs = info.MinArgs()
maxArgs = info.MaxArgs()
if info, exists, _ := GetFuncInfo(ctx, funcName); exists {
// ctx.RegisterFuncInfo(info)
ctx.RegisterFunc2(leftTerm.source(), info.Functor(), info.ReturnType(), info.Params())
} else if funcDef, ok := functor.(*funcDefFunctor); ok {
l := len(funcDef.params)
minArgs = l
maxArgs = l
paramCount := len(funcDef.params)
paramSpecs := make([]ExprFuncParam, paramCount)
for i := range paramSpecs {
paramSpecs[i] = newFuncParam(funcDef.params[i])
}
ctx.RegisterFunc2(leftTerm.source(), functor, typeAny, paramSpecs)
} else {
err = self.Errorf("unknown function %s()", funcName)
}
ctx.RegisterFunc(leftTerm.source(), functor, minArgs, maxArgs)
} else {
ctx.UnsafeSetVar(leftTerm.source(), v)
}

View File

@ -66,7 +66,10 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
v = leftValue
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
if functor, ok := rightValue.(Functor); ok {
ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
//ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
ctx.RegisterFunc2(leftTerm.source(), functor, typeAny, []ExprFuncParam{
newFuncParamFlag(paramValue, pfOptional|pfRepeat),
})
} else {
v = rightValue
ctx.UnsafeSetVar(leftTerm.source(), rightValue)

View File

@ -20,18 +20,18 @@ func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
var childValue any
var sourceCtx ExprContext
if len(self.children) == 0 {
if self.children == nil || len(self.children) == 0 {
sourceCtx = ctx
} else if self.children[0].symbol() == SymVariable && self.children[0].source() == "global" {
sourceCtx = globalCtx
} else if childValue, err = self.evalPrefix(ctx); err == nil {
if dc, ok := childValue.(*dataCursor); ok {
sourceCtx = dc.ctx
}
} else {
return
}
if sourceCtx != nil {
if formatter, ok := ctx.(Formatter); ok {
if formatter, ok := sourceCtx.(Formatter); ok {
v = formatter.ToString(0)
} else {
keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })

View File

@ -163,46 +163,32 @@ func TestGeneralParser(t *testing.T) {
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, "General", inputs, 102)
parserTest(t, "General", 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 {
var expr Expr
var gotResult any
var gotErr error
ctx := NewSimpleFuncStore()
parser := NewParser(ctx)
logTest(t, i+1, 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 [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
good = false
}
}
good := doTest(t, section, &input, i+1)
if good {
succeeded++
} else {
@ -212,6 +198,40 @@ func parserTest(t *testing.T, section string, inputs []inputType) {
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 := NewSimpleFuncStore()
parser := NewParser(ctx)
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 [%T], want = %v [%T]", count, input.source, gotResult, gotResult, input.wantResult, 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)

View File

@ -6,6 +6,7 @@ package expr
import (
"fmt"
"slices"
"strings"
)
@ -14,33 +15,98 @@ type SimpleFuncStore struct {
funcStore map[string]*funcInfo
}
type paramFlags uint16
const (
pfOptional paramFlags = 1 << iota
pfRepeat
)
type funcParamInfo struct {
name string
flags paramFlags
defaultValue any
}
func newFuncParam(name string) *funcParamInfo {
return &funcParamInfo{name: name}
}
func newFuncParamFlag(name string, flags paramFlags) *funcParamInfo {
return &funcParamInfo{name: name, flags: flags}
}
func newFuncParamFlagDef(name string, flags paramFlags, defValue any) *funcParamInfo {
return &funcParamInfo{name: name, flags: flags, defaultValue: defValue}
}
func (param *funcParamInfo) Name() string {
return param.name
}
func (param *funcParamInfo) Type() string {
return "any"
}
func (param *funcParamInfo) IsOptional() bool {
return (param.flags & pfOptional) != 0
}
func (param *funcParamInfo) IsRepeat() bool {
return (param.flags & pfRepeat) != 0
}
func (param *funcParamInfo) DefaultValue() any {
return param.defaultValue
}
type funcInfo struct {
name string
minArgs int
maxArgs int
functor Functor
params []ExprFuncParam
returnType string
}
func (info *funcInfo) Params() []ExprFuncParam {
return info.params
}
func (info *funcInfo) ReturnType() string {
return info.returnType
}
func (info *funcInfo) ToString(opt FmtOpt) string {
var sb strings.Builder
var i int
sb.WriteString("func(")
for i = 0; i < info.minArgs; i++ {
sb.WriteByte('(')
if info.params != nil {
for i, p := range info.params {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(fmt.Sprintf("arg%d", i+1))
sb.WriteString(p.Name())
if p.IsOptional() {
sb.WriteByte('=')
if s, ok := p.DefaultValue().(string); ok {
sb.WriteByte('"')
sb.WriteString(s)
sb.WriteByte('"')
} else {
sb.WriteString(fmt.Sprintf("%v", p.DefaultValue()))
}
}
}
for ; i < info.maxArgs; i++ {
sb.WriteString(fmt.Sprintf("arg%d", i+1))
}
if info.maxArgs < 0 {
if info.minArgs > 0 {
sb.WriteString(", ")
}
sb.WriteString(" ...")
}
sb.WriteString(") {...}")
sb.WriteString(") -> ")
if len(info.returnType) > 0 {
sb.WriteString(info.returnType)
} else {
sb.WriteString(typeAny)
}
return sb.String()
}
@ -65,7 +131,7 @@ func NewSimpleFuncStore() *SimpleFuncStore {
SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
funcStore: make(map[string]*funcInfo),
}
ImportBuiltinsFuncs(ctx)
//ImportBuiltinsFuncs(ctx)
return ctx
}
@ -81,7 +147,9 @@ func (ctx *SimpleFuncStore) Clone() ExprContext {
func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("funcs: {\n")
first := true
for _, name := range ctx.EnumFuncs(func(name string) bool { return true }) {
names := ctx.EnumFuncs(func(name string) bool { return true })
slices.Sort(names)
for _, name := range names {
if first {
first = false
} else {
@ -91,7 +159,7 @@ func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
value, _ := ctx.GetFuncInfo(name)
sb.WriteString(strings.Repeat("\t", indent+1))
sb.WriteString(name)
sb.WriteString("=")
//sb.WriteString("=")
if formatter, ok := value.(Formatter); ok {
sb.WriteString(formatter.ToString(0))
} else {
@ -113,8 +181,42 @@ func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc, exists bool
return
}
func (ctx *SimpleFuncStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
ctx.funcStore[name] = &funcInfo{name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor}
// func (ctx *SimpleFuncStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
// ctx.funcStore[name] = &funcInfo{name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor}
// }
func (ctx *SimpleFuncStore) RegisterFuncInfo(info ExprFunc) {
ctx.funcStore[info.Name()], _ = info.(*funcInfo)
}
func (ctx *SimpleFuncStore) RegisterFunc2(name string, functor Functor, returnType string, params []ExprFuncParam) error {
var minArgs = 0
var maxArgs = 0
if params != nil {
for _, p := range params {
if maxArgs == -1 {
return fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name())
// } else if p.IsRepeat() {
// maxArgs = -1
// continue
}
if p.IsOptional() {
maxArgs++
} else if maxArgs == minArgs {
minArgs++
maxArgs++
} else {
return fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name())
}
if p.IsRepeat() {
maxArgs = -1
}
}
}
ctx.funcStore[name] = &funcInfo{
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, params: params,
}
return nil
}
func (ctx *SimpleFuncStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {

View File

@ -72,7 +72,12 @@ func (ctx *SimpleVarStore) Call(name string, args []any) (result any, err error)
return
}
func (ctx *SimpleVarStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
// func (ctx *SimpleVarStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
// }
func (ctx *SimpleVarStore) RegisterFuncInfo(info ExprFunc) {
}
func (ctx *SimpleVarStore) RegisterFunc2(name string, f Functor, returnType string, param []ExprFuncParam) error {
return nil
}
func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {