Compare commits
5 Commits
f22b5a6f0b
...
e5f63c3d9d
Author | SHA1 | Date | |
---|---|---|---|
e5f63c3d9d | |||
d545a35acf | |||
e4275e2cb6 | |||
1ff5770264 | |||
ba32e2dccf |
@ -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"
|
||||
// )
|
||||
|
@ -5,10 +5,13 @@
|
||||
package expr
|
||||
|
||||
const (
|
||||
typeAny = "any"
|
||||
typeBoolean = "boolean"
|
||||
typeFloat = "decimal"
|
||||
typeFloat = "float"
|
||||
typeFraction = "fraction"
|
||||
typeHandle = "handle"
|
||||
typeInt = "integer"
|
||||
typeItem = "item"
|
||||
typeNumber = "number"
|
||||
typeString = "string"
|
||||
)
|
||||
|
@ -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.RegisterFunc(name, info.Functor(), info.ReturnType(), info.Params())
|
||||
}
|
||||
|
||||
func exportObjects(destCtx, sourceCtx ExprContext) {
|
||||
|
25
context.go
25
context.go
@ -4,28 +4,31 @@
|
||||
// context.go
|
||||
package expr
|
||||
|
||||
// ---- Function template
|
||||
type FuncTemplate func(ctx ExprContext, name string, args []any) (result any, err error)
|
||||
|
||||
// ---- Functor interface
|
||||
type Functor interface {
|
||||
Invoke(ctx ExprContext, name string, args []any) (result any, err error)
|
||||
SetFunc(info ExprFunc)
|
||||
GetFunc() ExprFunc
|
||||
}
|
||||
|
||||
type simpleFunctor struct {
|
||||
f FuncTemplate
|
||||
}
|
||||
|
||||
func (functor *simpleFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
return functor.f(ctx, name, args)
|
||||
// ---- Function Param Info
|
||||
type ExprFuncParam interface {
|
||||
Name() string
|
||||
Type() string
|
||||
IsOptional() bool
|
||||
IsRepeat() bool
|
||||
DefaultValue() any
|
||||
}
|
||||
|
||||
// ---- Function Info
|
||||
type ExprFunc interface {
|
||||
Formatter
|
||||
Name() string
|
||||
MinArgs() int
|
||||
MaxArgs() int
|
||||
Functor() Functor
|
||||
Params() []ExprFuncParam
|
||||
ReturnType() string
|
||||
}
|
||||
|
||||
// ----Expression Context
|
||||
@ -38,5 +41,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)
|
||||
RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) error
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ func TestDictParser(t *testing.T) {
|
||||
var gotResult any
|
||||
var gotErr error
|
||||
|
||||
ctx := NewSimpleFuncStore()
|
||||
ctx := NewSimpleStore()
|
||||
ctx.SetVar("var1", int64(123))
|
||||
ctx.SetVar("var2", "abc")
|
||||
ImportMathFuncs(ctx)
|
||||
|
@ -295,7 +295,7 @@ _item_ = _string-expr_ "**.**" _integer-expr_
|
||||
=== Booleans
|
||||
Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relational and boolean expressions result in boolean values.
|
||||
|
||||
.Relational operators
|
||||
.Relational operators^(*)^
|
||||
[cols="^1,^2,6,4"]
|
||||
|===
|
||||
| Symbol | Operation | Description | Examples
|
||||
@ -314,6 +314,8 @@ Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relationa
|
||||
[blue]`"b" \<= "b"` -> _true_
|
||||
|===
|
||||
|
||||
^(*)^ See also the [blue]`in` operator in the _list_ and _dictionary_ sections.
|
||||
|
||||
|
||||
.Boolean operators
|
||||
[cols="^2,^2,5,4"]
|
||||
@ -575,7 +577,7 @@ The table below shows all supported operators by decreasing priorities.
|
||||
| Priority | Operators | Position | Operation | Operands and results
|
||||
|
||||
.2+|*ITEM*| [blue]`.` | _Infix_ | _List item_| _list_ `"."` _integer_ -> _any_
|
||||
| [blue]`.` | _Infix_ | _Dict item_ | _dict_ `""` _any_ -> _any_
|
||||
| [blue]`.` | _Infix_ | _Dict item_ | _dict_ `"."` _any_ -> _any_
|
||||
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `"++"` -> _integer_
|
||||
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `"++"` -> _any_
|
||||
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `"!"` -> _integer_
|
||||
|
@ -585,7 +585,7 @@ pre.rouge .ss {
|
||||
<div class="sectionbody">
|
||||
<!-- toc disabled -->
|
||||
<div class="paragraph">
|
||||
<p><mark>TODO: Work in progress (last update on 2024/05/17, 15:47 p.m.)</mark></p>
|
||||
<p><mark>TODO: Work in progress (last update on 2024/05/20, 06:58 a.m.)</mark></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1041,7 +1041,7 @@ pre.rouge .ss {
|
||||
<p>Boolean data type has two values only: <em class="blue">true</em> and <em class="blue">false</em>. Relational and boolean expressions result in boolean values.</p>
|
||||
</div>
|
||||
<table class="tableblock frame-all grid-all stretch">
|
||||
<caption class="title">Table 4. Relational operators</caption>
|
||||
<caption class="title">Table 4. Relational operators<sup>(*)</sup></caption>
|
||||
<colgroup>
|
||||
<col style="width: 7.6923%;">
|
||||
<col style="width: 15.3846%;">
|
||||
@ -1101,6 +1101,9 @@ pre.rouge .ss {
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="paragraph">
|
||||
<p><sup>(*)</sup> See also the <code class="blue">in</code> operator in the <em>list</em> and <em>dictionary</em> sections.</p>
|
||||
</div>
|
||||
<table class="tableblock frame-all grid-all stretch">
|
||||
<caption class="title">Table 5. Boolean operators</caption>
|
||||
<colgroup>
|
||||
@ -1871,7 +1874,7 @@ These operators have a high priority, in particular higher than the operator <co
|
||||
</div>
|
||||
<div id="footer">
|
||||
<div id="footer-text">
|
||||
Last updated 2024-05-20 06:58:09 +0200
|
||||
Last updated 2024-05-20 09:40:23 +0200
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
@ -44,7 +44,7 @@ func TestExpr(t *testing.T) {
|
||||
var gotResult any
|
||||
var gotErr error
|
||||
|
||||
ctx := NewSimpleFuncStore()
|
||||
ctx := NewSimpleStore()
|
||||
// ImportMathFuncs(ctx)
|
||||
// ImportImportFunc(ctx)
|
||||
ImportOsFuncs(ctx)
|
||||
|
67
func-base.go
67
func-base.go
@ -54,6 +54,24 @@ func isDictionaryFunc(ctx ExprContext, name string, args []any) (result any, err
|
||||
return
|
||||
}
|
||||
|
||||
func boolFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
switch v := args[0].(type) {
|
||||
case int64:
|
||||
result = (v != 0)
|
||||
case *fraction:
|
||||
result = v.num != 0
|
||||
case float64:
|
||||
result = v != 0.0
|
||||
case bool:
|
||||
result = v
|
||||
case string:
|
||||
result = len(v) > 0
|
||||
default:
|
||||
err = errCantConvert(name, v, "bool")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
switch v := args[0].(type) {
|
||||
case int64:
|
||||
@ -119,10 +137,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 +145,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 +158,27 @@ 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", newGolangFunctor(isNilFunc), typeBoolean, anyParams)
|
||||
ctx.RegisterFunc("isInt", newGolangFunctor(isIntFunc), typeBoolean, anyParams)
|
||||
ctx.RegisterFunc("isFloat", newGolangFunctor(isFloatFunc), typeBoolean, anyParams)
|
||||
ctx.RegisterFunc("isBool", newGolangFunctor(isBoolFunc), typeBoolean, anyParams)
|
||||
ctx.RegisterFunc("isString", newGolangFunctor(isStringFunc), typeBoolean, anyParams)
|
||||
ctx.RegisterFunc("isFract", newGolangFunctor(isFractionFunc), typeBoolean, anyParams)
|
||||
ctx.RegisterFunc("isRational", newGolangFunctor(isRationalFunc), typeBoolean, anyParams)
|
||||
ctx.RegisterFunc("isList", newGolangFunctor(isListFunc), typeBoolean, anyParams)
|
||||
ctx.RegisterFunc("isDict", newGolangFunctor(isDictionaryFunc), typeBoolean, anyParams)
|
||||
|
||||
ctx.RegisterFunc("bool", newGolangFunctor(boolFunc), typeBoolean, anyParams)
|
||||
ctx.RegisterFunc("int", newGolangFunctor(intFunc), typeInt, anyParams)
|
||||
ctx.RegisterFunc("dec", newGolangFunctor(decFunc), typeFloat, anyParams)
|
||||
ctx.RegisterFunc("fract", newGolangFunctor(fractFunc), typeFraction, []ExprFuncParam{
|
||||
newFuncParam(paramValue),
|
||||
newFuncParamFlagDef("denominator", pfOptional, 1),
|
||||
})
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -137,8 +137,12 @@ 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", newGolangFunctor(importFunc), typeAny, []ExprFuncParam{
|
||||
newFuncParamFlag(typeFilepath, pfRepeat),
|
||||
})
|
||||
ctx.RegisterFunc("importAll", newGolangFunctor(importAllFunc), typeAny, []ExprFuncParam{
|
||||
newFuncParamFlag(typeFilepath, pfRepeat),
|
||||
})
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -167,8 +167,13 @@ 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", &golangFunctor{f: addFunc}, typeNumber, []ExprFuncParam{
|
||||
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 0),
|
||||
})
|
||||
|
||||
ctx.RegisterFunc("mul", &golangFunctor{f: mulFunc}, typeNumber, []ExprFuncParam{
|
||||
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 1),
|
||||
})
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
26
func-os.go
26
func-os.go
@ -158,12 +158,26 @@ 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", newGolangFunctor(openFileFunc), typeHandle, []ExprFuncParam{
|
||||
newFuncParam(typeFilepath),
|
||||
})
|
||||
ctx.RegisterFunc("appendFile", newGolangFunctor(appendFileFunc), typeHandle, []ExprFuncParam{
|
||||
newFuncParam(typeFilepath),
|
||||
})
|
||||
ctx.RegisterFunc("createFile", newGolangFunctor(createFileFunc), typeHandle, []ExprFuncParam{
|
||||
newFuncParam(typeFilepath),
|
||||
})
|
||||
ctx.RegisterFunc("writeFile", newGolangFunctor(writeFileFunc), typeInt, []ExprFuncParam{
|
||||
newFuncParam(typeHandle),
|
||||
newFuncParamFlagDef(typeItem, pfOptional|pfRepeat, ""),
|
||||
})
|
||||
ctx.RegisterFunc("readFile", newGolangFunctor(readFileFunc), typeString, []ExprFuncParam{
|
||||
newFuncParam(typeHandle),
|
||||
newFuncParamFlagDef("limitCh", pfOptional, "\\n"),
|
||||
})
|
||||
ctx.RegisterFunc("closeFile", newGolangFunctor(closeFileFunc), typeBoolean, []ExprFuncParam{
|
||||
newFuncParam(typeHandle),
|
||||
})
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -33,9 +33,9 @@ func doJoinStr(funcName string, sep string, it Iterator) (result any, err error)
|
||||
}
|
||||
|
||||
func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
// if len(args) < 1 {
|
||||
// return nil, errMissingRequiredParameter(name, paramSeparator)
|
||||
// }
|
||||
// if len(args) < 1 {
|
||||
// return nil, errMissingRequiredParameter(name, paramSeparator)
|
||||
// }
|
||||
if sep, ok := args[0].(string); ok {
|
||||
if len(args) == 1 {
|
||||
result = ""
|
||||
@ -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.RegisterFunc("joinStr", newGolangFunctor(joinStrFunc), typeString, []ExprFuncParam{
|
||||
newFuncParam(paramSeparator),
|
||||
newFuncParamFlagDef(paramItem, pfOptional|pfRepeat, ""),
|
||||
})
|
||||
|
||||
ctx.RegisterFunc("subStr", newGolangFunctor(subStrFunc), typeString, []ExprFuncParam{
|
||||
newFuncParam(paramSource),
|
||||
newFuncParamFlagDef(paramStart, pfOptional, 0),
|
||||
newFuncParamFlagDef(paramCount, pfOptional, -1),
|
||||
})
|
||||
|
||||
ctx.RegisterFunc("splitStr", newGolangFunctor(splitStrFunc), "list of "+typeString, []ExprFuncParam{
|
||||
newFuncParam(paramSource),
|
||||
newFuncParamFlagDef(paramSeparator, pfOptional, ""),
|
||||
newFuncParamFlagDef(paramCount, pfOptional, -1),
|
||||
})
|
||||
|
||||
ctx.RegisterFunc("trimStr", newGolangFunctor(trimStrFunc), typeString, []ExprFuncParam{
|
||||
newFuncParam(paramSource),
|
||||
})
|
||||
|
||||
ctx.RegisterFunc("startsWithStr", newGolangFunctor(startsWithStrFunc), typeBoolean, []ExprFuncParam{
|
||||
newFuncParam(paramSource),
|
||||
newFuncParamFlag(paramPrefix, pfRepeat),
|
||||
})
|
||||
|
||||
ctx.RegisterFunc("endsWithStr", newGolangFunctor(endsWithStrFunc), typeBoolean, []ExprFuncParam{
|
||||
newFuncParam(paramSource),
|
||||
newFuncParamFlag(paramSuffix, pfRepeat),
|
||||
})
|
||||
}
|
||||
|
||||
// Register the import function in the import-register.
|
||||
|
@ -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)
|
||||
}
|
||||
|
232
function.go
Normal file
232
function.go
Normal file
@ -0,0 +1,232 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// function.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ---- Function template
|
||||
type FuncTemplate func(ctx ExprContext, name string, args []any) (result any, err error)
|
||||
|
||||
// ---- Common functor definition
|
||||
type baseFunctor struct {
|
||||
info ExprFunc
|
||||
}
|
||||
|
||||
func (functor *baseFunctor) ToString(opt FmtOpt) (s string) {
|
||||
if functor.info != nil {
|
||||
s = functor.info.ToString(opt)
|
||||
} else {
|
||||
s = "func() {<body>}"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (functor *baseFunctor) SetFunc(info ExprFunc) {
|
||||
functor.info = info
|
||||
}
|
||||
|
||||
func (functor *baseFunctor) GetFunc() ExprFunc {
|
||||
return functor.info
|
||||
}
|
||||
|
||||
// ---- Linking with the functions of Go
|
||||
type golangFunctor struct {
|
||||
baseFunctor
|
||||
f FuncTemplate
|
||||
}
|
||||
|
||||
func newGolangFunctor(f FuncTemplate) *golangFunctor {
|
||||
return &golangFunctor{f: f}
|
||||
}
|
||||
|
||||
func (functor *golangFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
return functor.f(ctx, name, args)
|
||||
}
|
||||
|
||||
// ---- Linking with the functions of Expr
|
||||
type exprFunctor struct {
|
||||
baseFunctor
|
||||
params []string
|
||||
expr Expr
|
||||
}
|
||||
|
||||
func newExprFunctor(e Expr, params []string) *exprFunctor {
|
||||
return &exprFunctor{expr: e, params: params}
|
||||
}
|
||||
|
||||
func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
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)
|
||||
ctx.RegisterFunc(p, funcArg, typeAny, []ExprFuncParam{
|
||||
newFuncParam(paramValue),
|
||||
})
|
||||
} else {
|
||||
ctx.UnsafeSetVar(p, arg)
|
||||
}
|
||||
} else {
|
||||
ctx.UnsafeSetVar(p, nil)
|
||||
}
|
||||
}
|
||||
result, err = functor.expr.eval(ctx, false)
|
||||
return
|
||||
}
|
||||
|
||||
// ---- Function Parameters
|
||||
type paramFlags uint16
|
||||
|
||||
const (
|
||||
pfOptional paramFlags = 1 << iota
|
||||
pfRepeat
|
||||
)
|
||||
|
||||
type funcParamInfo struct {
|
||||
name string
|
||||
flags paramFlags
|
||||
defaultValue any
|
||||
}
|
||||
|
||||
func newFuncParam(name string) ExprFuncParam {
|
||||
return &funcParamInfo{name: name}
|
||||
}
|
||||
|
||||
func newFuncParamFlag(name string, flags paramFlags) ExprFuncParam {
|
||||
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
|
||||
}
|
||||
|
||||
// --- Functions
|
||||
type funcInfo struct {
|
||||
name string
|
||||
minArgs int
|
||||
maxArgs int
|
||||
functor Functor
|
||||
params []ExprFuncParam
|
||||
returnType string
|
||||
}
|
||||
|
||||
func newFuncInfo(name string, functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
|
||||
var minArgs = 0
|
||||
var maxArgs = 0
|
||||
if params != nil {
|
||||
for _, p := range params {
|
||||
if maxArgs == -1 {
|
||||
return nil, fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name())
|
||||
}
|
||||
if p.IsOptional() {
|
||||
maxArgs++
|
||||
} else if maxArgs == minArgs {
|
||||
minArgs++
|
||||
maxArgs++
|
||||
} else {
|
||||
return nil, fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name())
|
||||
}
|
||||
if p.IsRepeat() {
|
||||
maxArgs = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
info = &funcInfo{
|
||||
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, params: 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 (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
|
||||
if len(info.Name()) == 0 {
|
||||
sb.WriteString("func")
|
||||
} else {
|
||||
sb.WriteString(info.Name())
|
||||
}
|
||||
sb.WriteByte('(')
|
||||
if info.params != nil {
|
||||
for i, p := range info.params {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if info.maxArgs < 0 {
|
||||
sb.WriteString(" ...")
|
||||
}
|
||||
sb.WriteString("): ")
|
||||
if len(info.returnType) > 0 {
|
||||
sb.WriteString(info.returnType)
|
||||
} else {
|
||||
sb.WriteString(typeAny)
|
||||
}
|
||||
sb.WriteString(" {<body>}")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (info *funcInfo) Name() string {
|
||||
return info.name
|
||||
}
|
||||
|
||||
func (info *funcInfo) MinArgs() int {
|
||||
return info.minArgs
|
||||
}
|
||||
|
||||
func (info *funcInfo) MaxArgs() int {
|
||||
return info.maxArgs
|
||||
}
|
||||
|
||||
func (info *funcInfo) Functor() Functor {
|
||||
return info.functor
|
||||
}
|
@ -6,7 +6,7 @@ package expr
|
||||
|
||||
import "path/filepath"
|
||||
|
||||
var globalCtx *SimpleFuncStore
|
||||
var globalCtx *SimpleStore
|
||||
|
||||
func ImportInContext(name string) (exists bool) {
|
||||
var mod *module
|
||||
@ -50,5 +50,6 @@ func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool, owne
|
||||
}
|
||||
|
||||
func init() {
|
||||
globalCtx = NewSimpleFuncStore()
|
||||
globalCtx = NewSimpleStore()
|
||||
ImportBuiltinsFuncs(globalCtx)
|
||||
}
|
||||
|
@ -34,12 +34,15 @@ func EvalStringA(source string, args ...Arg) (result any, err error) {
|
||||
}
|
||||
|
||||
func EvalStringV(source string, args []Arg) (result any, err error) {
|
||||
ctx := NewSimpleFuncStore()
|
||||
ctx := NewSimpleStore()
|
||||
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 := newGolangFunctor(f)
|
||||
// ctx.RegisterFunc(arg.Name, functor, 0, -1)
|
||||
ctx.RegisterFunc(arg.Name, functor, typeAny, []ExprFuncParam{
|
||||
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 0),
|
||||
})
|
||||
} else {
|
||||
err = fmt.Errorf("invalid function specification: %q", arg.Name)
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ func TestEvalStringA(t *testing.T) {
|
||||
|
||||
func TestEvalString(t *testing.T) {
|
||||
|
||||
ctx := NewSimpleVarStore()
|
||||
ctx := NewSimpleStore()
|
||||
ctx.SetVar("a", uint8(1))
|
||||
ctx.SetVar("b", int8(2))
|
||||
ctx.SetVar("f", 2.0)
|
||||
|
@ -60,7 +60,7 @@ func TestListParser(t *testing.T) {
|
||||
var gotResult any
|
||||
var gotErr error
|
||||
|
||||
ctx := NewSimpleFuncStore()
|
||||
ctx := NewSimpleStore()
|
||||
// ctx.SetVar("var1", int64(123))
|
||||
// ctx.SetVar("var2", "abc")
|
||||
ImportMathFuncs(ctx)
|
||||
|
@ -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)
|
||||
@ -75,27 +76,30 @@ func newFuncDefTerm(tk *Token, args []*term) *term {
|
||||
|
||||
// -------- eval func def
|
||||
// TODO
|
||||
type funcDefFunctor struct {
|
||||
params []string
|
||||
expr Expr
|
||||
}
|
||||
// type funcDefFunctor struct {
|
||||
// params []string
|
||||
// expr Expr
|
||||
// }
|
||||
|
||||
func (functor *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
for i, p := range functor.params {
|
||||
if i < len(args) {
|
||||
arg := args[i]
|
||||
if functor, ok := arg.(Functor); ok {
|
||||
ctx.RegisterFunc(p, functor, 0, -1)
|
||||
} else {
|
||||
ctx.UnsafeSetVar(p, arg)
|
||||
}
|
||||
} else {
|
||||
ctx.UnsafeSetVar(p, nil)
|
||||
}
|
||||
}
|
||||
result, err = functor.expr.eval(ctx, false)
|
||||
return
|
||||
}
|
||||
// func (funcDef *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
// for i, p := range funcDef.params {
|
||||
// if i < len(args) {
|
||||
// arg := args[i]
|
||||
// if functor, ok := arg.(Functor); ok {
|
||||
// // ctx.RegisterFunc(p, functor, 0, -1)
|
||||
// ctx.RegisterFunc2(p, functor, typeAny, []ExprFuncParam{
|
||||
// newFuncParam(paramValue),
|
||||
// })
|
||||
// } else {
|
||||
// ctx.UnsafeSetVar(p, arg)
|
||||
// }
|
||||
// } else {
|
||||
// ctx.UnsafeSetVar(p, nil)
|
||||
// }
|
||||
// }
|
||||
// result, err = funcDef.expr.eval(ctx, false)
|
||||
// return
|
||||
// }
|
||||
|
||||
func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
|
||||
bodySpec := self.value()
|
||||
@ -104,10 +108,11 @@ func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
|
||||
for _, param := range self.children {
|
||||
paramList = append(paramList, param.source())
|
||||
}
|
||||
v = &funcDefFunctor{
|
||||
params: paramList,
|
||||
expr: expr,
|
||||
}
|
||||
v = newExprFunctor(expr, paramList)
|
||||
// v = &funcDefFunctor{
|
||||
// params: paramList,
|
||||
// expr: expr,
|
||||
// }
|
||||
} else {
|
||||
err = errors.New("invalid function definition: the body specification must be an expression")
|
||||
}
|
||||
|
@ -74,7 +74,8 @@ func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map
|
||||
ds = make(map[string]Functor)
|
||||
for keyAny, item := range *dictAny {
|
||||
if key, ok := keyAny.(string); ok {
|
||||
if functor, ok := item.(*funcDefFunctor); ok {
|
||||
//if functor, ok := item.(*funcDefFunctor); ok {
|
||||
if functor, ok := item.(Functor); ok {
|
||||
ds[key] = functor
|
||||
if index := slices.Index(requiredFields, key); index >= 0 {
|
||||
foundFields |= 1 << index
|
||||
|
@ -31,18 +31,21 @@ 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()
|
||||
} else if funcDef, ok := functor.(*funcDefFunctor); ok {
|
||||
l := len(funcDef.params)
|
||||
minArgs = l
|
||||
maxArgs = l
|
||||
if info, exists, _ := GetFuncInfo(ctx, funcName); exists {
|
||||
// ctx.RegisterFuncInfo(info)
|
||||
ctx.RegisterFunc(leftTerm.source(), info.Functor(), info.ReturnType(), info.Params())
|
||||
} else if funcDef, ok := functor.(*exprFunctor); ok {
|
||||
paramSpecs := ForAll(funcDef.params, newFuncParam)
|
||||
// paramCount := len(funcDef.params)
|
||||
// paramSpecs := make([]ExprFuncParam, paramCount)
|
||||
// for i := range paramSpecs {
|
||||
// paramSpecs[i] = newFuncParam(funcDef.params[i])
|
||||
// }
|
||||
ctx.RegisterFunc(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)
|
||||
}
|
||||
|
@ -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.RegisterFunc(leftTerm.source(), functor, typeAny, []ExprFuncParam{
|
||||
newFuncParamFlag(paramValue, pfOptional|pfRepeat),
|
||||
})
|
||||
} else {
|
||||
v = rightValue
|
||||
ctx.UnsafeSetVar(leftTerm.source(), rightValue)
|
||||
|
@ -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] != '_' })
|
||||
|
@ -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 := NewSimpleStore()
|
||||
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)
|
||||
|
@ -1,142 +0,0 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// simple-func-store.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SimpleFuncStore struct {
|
||||
SimpleVarStore
|
||||
funcStore map[string]*funcInfo
|
||||
}
|
||||
|
||||
type funcInfo struct {
|
||||
name string
|
||||
minArgs int
|
||||
maxArgs int
|
||||
functor Functor
|
||||
}
|
||||
|
||||
func (info *funcInfo) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
var i int
|
||||
sb.WriteString("func(")
|
||||
for i = 0; i < info.minArgs; i++ {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("arg%d", i+1))
|
||||
}
|
||||
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(") {...}")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (info *funcInfo) Name() string {
|
||||
return info.name
|
||||
}
|
||||
|
||||
func (info *funcInfo) MinArgs() int {
|
||||
return info.minArgs
|
||||
}
|
||||
|
||||
func (info *funcInfo) MaxArgs() int {
|
||||
return info.maxArgs
|
||||
}
|
||||
|
||||
func (info *funcInfo) Functor() Functor {
|
||||
return info.functor
|
||||
}
|
||||
|
||||
func NewSimpleFuncStore() *SimpleFuncStore {
|
||||
ctx := &SimpleFuncStore{
|
||||
SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
|
||||
funcStore: make(map[string]*funcInfo),
|
||||
}
|
||||
ImportBuiltinsFuncs(ctx)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) Clone() ExprContext {
|
||||
svs := ctx.SimpleVarStore
|
||||
return &SimpleFuncStore{
|
||||
// SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
|
||||
SimpleVarStore: SimpleVarStore{varStore: svs.cloneVars()},
|
||||
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
|
||||
}
|
||||
}
|
||||
|
||||
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 }) {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
sb.WriteByte(',')
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
value, _ := ctx.GetFuncInfo(name)
|
||||
sb.WriteString(strings.Repeat("\t", indent+1))
|
||||
sb.WriteString(name)
|
||||
sb.WriteString("=")
|
||||
if formatter, ok := value.(Formatter); ok {
|
||||
sb.WriteString(formatter.ToString(0))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", value))
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n}\n")
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(ctx.SimpleVarStore.ToString(opt))
|
||||
funcsCtxToBuilder(&sb, ctx, 0)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
|
||||
info, exists = ctx.funcStore[name]
|
||||
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) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
|
||||
funcNames = make([]string, 0)
|
||||
for name := range ctx.funcStore {
|
||||
if acceptor != nil {
|
||||
if acceptor(name) {
|
||||
funcNames = append(funcNames, name)
|
||||
}
|
||||
} else {
|
||||
funcNames = append(funcNames, name)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) Call(name string, args []any) (result any, err error) {
|
||||
if info, exists := ctx.funcStore[name]; exists {
|
||||
functor := info.functor
|
||||
result, err = functor.Invoke(ctx, name, args)
|
||||
} else {
|
||||
err = fmt.Errorf("unknown function %s()", name)
|
||||
}
|
||||
return
|
||||
}
|
244
simple-func-store.go.bak
Normal file
244
simple-func-store.go.bak
Normal file
@ -0,0 +1,244 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// simple-func-store.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SimpleFuncStore struct {
|
||||
SimpleVarStore
|
||||
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
|
||||
sb.WriteByte('(')
|
||||
if info.params != nil {
|
||||
for i, p := range info.params {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if info.maxArgs < 0 {
|
||||
sb.WriteString(" ...")
|
||||
}
|
||||
sb.WriteString(") -> ")
|
||||
if len(info.returnType) > 0 {
|
||||
sb.WriteString(info.returnType)
|
||||
} else {
|
||||
sb.WriteString(typeAny)
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (info *funcInfo) Name() string {
|
||||
return info.name
|
||||
}
|
||||
|
||||
func (info *funcInfo) MinArgs() int {
|
||||
return info.minArgs
|
||||
}
|
||||
|
||||
func (info *funcInfo) MaxArgs() int {
|
||||
return info.maxArgs
|
||||
}
|
||||
|
||||
func (info *funcInfo) Functor() Functor {
|
||||
return info.functor
|
||||
}
|
||||
|
||||
func NewSimpleFuncStore() *SimpleFuncStore {
|
||||
ctx := &SimpleFuncStore{
|
||||
SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
|
||||
funcStore: make(map[string]*funcInfo),
|
||||
}
|
||||
//ImportBuiltinsFuncs(ctx)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) Clone() ExprContext {
|
||||
svs := ctx.SimpleVarStore
|
||||
return &SimpleFuncStore{
|
||||
// SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
|
||||
SimpleVarStore: SimpleVarStore{varStore: svs.cloneVars()},
|
||||
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
sb.WriteString(name)
|
||||
//sb.WriteString("=")
|
||||
if formatter, ok := value.(Formatter); ok {
|
||||
sb.WriteString(formatter.ToString(0))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", value))
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n}\n")
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(ctx.SimpleVarStore.ToString(opt))
|
||||
funcsCtxToBuilder(&sb, ctx, 0)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
|
||||
info, exists = ctx.funcStore[name]
|
||||
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) 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) {
|
||||
funcNames = make([]string, 0)
|
||||
for name := range ctx.funcStore {
|
||||
if acceptor != nil {
|
||||
if acceptor(name) {
|
||||
funcNames = append(funcNames, name)
|
||||
}
|
||||
} else {
|
||||
funcNames = append(funcNames, name)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) Call(name string, args []any) (result any, err error) {
|
||||
if info, exists := ctx.funcStore[name]; exists {
|
||||
functor := info.functor
|
||||
result, err = functor.Invoke(ctx, name, args)
|
||||
} else {
|
||||
err = fmt.Errorf("unknown function %s()", name)
|
||||
}
|
||||
return
|
||||
}
|
173
simple-store.go
Normal file
173
simple-store.go
Normal file
@ -0,0 +1,173 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// simple-store.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SimpleStore struct {
|
||||
varStore map[string]any
|
||||
funcStore map[string]*funcInfo
|
||||
}
|
||||
|
||||
func NewSimpleStore() *SimpleStore {
|
||||
ctx := &SimpleStore{
|
||||
varStore: make(map[string]any),
|
||||
funcStore: make(map[string]*funcInfo),
|
||||
}
|
||||
//ImportBuiltinsFuncs(ctx)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) Clone() ExprContext {
|
||||
return &SimpleStore{
|
||||
varStore: CloneFilteredMap(ctx.varStore, func(name string) bool { return name[0] != '@' }),
|
||||
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
|
||||
}
|
||||
}
|
||||
|
||||
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
||||
sb.WriteString("vars: {\n")
|
||||
first := true
|
||||
for _, name := range ctx.EnumVars(func(name string) bool { return name[0] != '_' }) {
|
||||
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}\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))
|
||||
sb.WriteString(name)
|
||||
//sb.WriteString("=")
|
||||
if formatter, ok := value.(Formatter); ok {
|
||||
sb.WriteString(formatter.ToString(0))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", value))
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n}\n")
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
varsCtxToBuilder(&sb, ctx, 0)
|
||||
funcsCtxToBuilder(&sb, ctx, 0)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) GetVar(varName string) (v any, exists bool) {
|
||||
v, exists = ctx.varStore[varName]
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) UnsafeSetVar(varName string, value any) {
|
||||
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
|
||||
ctx.varStore[varName] = value
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) SetVar(varName string, value any) {
|
||||
// fmt.Printf("[%p] SetVar(%v, %v)\n", ctx, varName, value)
|
||||
if allowedValue, ok := fromGenericAny(value); ok {
|
||||
ctx.varStore[varName] = allowedValue
|
||||
} else {
|
||||
panic(fmt.Errorf("unsupported type %T of value %v", value, value))
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) EnumVars(acceptor func(name string) (accept bool)) (varNames []string) {
|
||||
varNames = make([]string, 0)
|
||||
for name := range ctx.varStore {
|
||||
if acceptor != nil {
|
||||
if acceptor(name) {
|
||||
varNames = append(varNames, name)
|
||||
}
|
||||
} else {
|
||||
varNames = append(varNames, name)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
|
||||
info, exists = ctx.funcStore[name]
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) RegisterFuncInfo(info ExprFunc) {
|
||||
ctx.funcStore[info.Name()], _ = info.(*funcInfo)
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) RegisterFunc(name string, functor Functor, returnType string, params []ExprFuncParam) (err error) {
|
||||
var info *funcInfo
|
||||
if info, err = newFuncInfo(name, functor, returnType, params); err == nil {
|
||||
ctx.funcStore[name] = info
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
|
||||
funcNames = make([]string, 0)
|
||||
for name := range ctx.funcStore {
|
||||
if acceptor != nil {
|
||||
if acceptor(name) {
|
||||
funcNames = append(funcNames, name)
|
||||
}
|
||||
} else {
|
||||
funcNames = append(funcNames, name)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) Call(name string, args []any) (result any, err error) {
|
||||
if info, exists := ctx.funcStore[name]; exists {
|
||||
functor := info.functor
|
||||
result, err = functor.Invoke(ctx, name, args)
|
||||
} else {
|
||||
err = fmt.Errorf("unknown function %s()", name)
|
||||
}
|
||||
return
|
||||
}
|
@ -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) {
|
Loading…
Reference in New Issue
Block a user