Compare commits

...

5 Commits

30 changed files with 970 additions and 308 deletions

View File

@ -5,7 +5,26 @@
package expr package expr
const ( const (
paramCount = "count"
paramItem = "item"
paramParts = "parts" paramParts = "parts"
paramSeparator = "separator" paramSeparator = "separator"
paramSource = "source" 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 package expr
const ( const (
typeAny = "any"
typeBoolean = "boolean" typeBoolean = "boolean"
typeFloat = "decimal" typeFloat = "float"
typeFraction = "fraction" typeFraction = "fraction"
typeHandle = "handle"
typeInt = "integer" typeInt = "integer"
typeItem = "item"
typeNumber = "number" typeNumber = "number"
typeString = "string" typeString = "string"
) )

View File

@ -22,7 +22,9 @@ func exportFunc(ctx ExprContext, name string, info ExprFunc) {
if name[0] == '@' { if name[0] == '@' {
name = name[1:] 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) { func exportObjects(destCtx, sourceCtx ExprContext) {

View File

@ -4,28 +4,31 @@
// context.go // context.go
package expr package expr
// ---- Function template
type FuncTemplate func(ctx ExprContext, name string, args []any) (result any, err error)
// ---- Functor interface // ---- Functor interface
type Functor interface { type Functor interface {
Invoke(ctx ExprContext, name string, args []any) (result any, err error) Invoke(ctx ExprContext, name string, args []any) (result any, err error)
SetFunc(info ExprFunc)
GetFunc() ExprFunc
} }
type simpleFunctor struct { // ---- Function Param Info
f FuncTemplate type ExprFuncParam interface {
} Name() string
Type() string
func (functor *simpleFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) { IsOptional() bool
return functor.f(ctx, name, args) IsRepeat() bool
DefaultValue() any
} }
// ---- Function Info // ---- Function Info
type ExprFunc interface { type ExprFunc interface {
Formatter
Name() string Name() string
MinArgs() int MinArgs() int
MaxArgs() int MaxArgs() int
Functor() Functor Functor() Functor
Params() []ExprFuncParam
ReturnType() string
} }
// ----Expression Context // ----Expression Context
@ -38,5 +41,7 @@ type ExprContext interface {
EnumFuncs(func(name string) (accept bool)) (funcNames []string) EnumFuncs(func(name string) (accept bool)) (funcNames []string)
GetFuncInfo(name string) (item ExprFunc, exists bool) GetFuncInfo(name string) (item ExprFunc, exists bool)
Call(name string, args []any) (result any, err error) 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
} }

View File

@ -42,7 +42,7 @@ func TestDictParser(t *testing.T) {
var gotResult any var gotResult any
var gotErr error var gotErr error
ctx := NewSimpleFuncStore() ctx := NewSimpleStore()
ctx.SetVar("var1", int64(123)) ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc") ctx.SetVar("var2", "abc")
ImportMathFuncs(ctx) ImportMathFuncs(ctx)

View File

@ -295,7 +295,7 @@ _item_ = _string-expr_ "**.**" _integer-expr_
=== Booleans === Booleans
Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relational and boolean expressions result in boolean values. 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"] [cols="^1,^2,6,4"]
|=== |===
| Symbol | Operation | Description | Examples | 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_ [blue]`"b" \<= "b"` -> _true_
|=== |===
^(*)^ See also the [blue]`in` operator in the _list_ and _dictionary_ sections.
.Boolean operators .Boolean operators
[cols="^2,^2,5,4"] [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 | Priority | Operators | Position | Operation | Operands and results
.2+|*ITEM*| [blue]`.` | _Infix_ | _List item_| _list_ `"."` _integer_ -> _any_ .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_ .2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `"++"` -> _integer_
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `"++"` -> _any_ | [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `"++"` -> _any_
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `"!"` -> _integer_ .1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `"!"` -> _integer_

View File

@ -585,7 +585,7 @@ pre.rouge .ss {
<div class="sectionbody"> <div class="sectionbody">
<!-- toc disabled --> <!-- toc disabled -->
<div class="paragraph"> <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> </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> <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> </div>
<table class="tableblock frame-all grid-all stretch"> <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> <colgroup>
<col style="width: 7.6923%;"> <col style="width: 7.6923%;">
<col style="width: 15.3846%;"> <col style="width: 15.3846%;">
@ -1101,6 +1101,9 @@ pre.rouge .ss {
</tr> </tr>
</tbody> </tbody>
</table> </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"> <table class="tableblock frame-all grid-all stretch">
<caption class="title">Table 5. Boolean operators</caption> <caption class="title">Table 5. Boolean operators</caption>
<colgroup> <colgroup>
@ -1871,7 +1874,7 @@ These operators have a high priority, in particular higher than the operator <co
</div> </div>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated 2024-05-20 06:58:09 +0200 Last updated 2024-05-20 09:40:23 +0200
</div> </div>
</div> </div>
</body> </body>

View File

@ -44,7 +44,7 @@ func TestExpr(t *testing.T) {
var gotResult any var gotResult any
var gotErr error var gotErr error
ctx := NewSimpleFuncStore() ctx := NewSimpleStore()
// ImportMathFuncs(ctx) // ImportMathFuncs(ctx)
// ImportImportFunc(ctx) // ImportImportFunc(ctx)
ImportOsFuncs(ctx) ImportOsFuncs(ctx)

View File

@ -54,6 +54,24 @@ func isDictionaryFunc(ctx ExprContext, name string, args []any) (result any, err
return 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) { func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
switch v := args[0].(type) { switch v := args[0].(type) {
case int64: case int64:
@ -119,10 +137,6 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
} }
case float64: case float64:
result, err = float64ToFraction(v) result, err = float64ToFraction(v)
// var n, d int64
// if n, d, err = float64ToFraction(v); err == nil {
// result = newFraction(n, d)
// }
case bool: case bool:
if v { if v {
result = newFraction(1, 1) result = newFraction(1, 1)
@ -131,16 +145,6 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
} }
case string: case string:
result, err = makeGeneratingFraction(v) 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: case *fraction:
result = v result = v
default: default:
@ -154,20 +158,27 @@ func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err err
} }
func ImportBuiltinsFuncs(ctx ExprContext) { func ImportBuiltinsFuncs(ctx ExprContext) {
ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, 1) anyParams := []ExprFuncParam{
ctx.RegisterFunc("isInt", &simpleFunctor{f: isIntFunc}, 1, 1) newFuncParam(paramValue),
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("isNil", newGolangFunctor(isNilFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isFraction", &simpleFunctor{f: isFractionFunc}, 1, 1) ctx.RegisterFunc("isInt", newGolangFunctor(isIntFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isFract", &simpleFunctor{f: isFractionFunc}, 1, 1) ctx.RegisterFunc("isFloat", newGolangFunctor(isFloatFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isRational", &simpleFunctor{f: isRationalFunc}, 1, 1) ctx.RegisterFunc("isBool", newGolangFunctor(isBoolFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isList", &simpleFunctor{f: isListFunc}, 1, 1) ctx.RegisterFunc("isString", newGolangFunctor(isStringFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isDictionary", &simpleFunctor{f: isDictionaryFunc}, 1, 1) ctx.RegisterFunc("isFract", newGolangFunctor(isFractionFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isDict", &simpleFunctor{f: isDictionaryFunc}, 1, 1) ctx.RegisterFunc("isRational", newGolangFunctor(isRationalFunc), typeBoolean, anyParams)
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, 1) ctx.RegisterFunc("isList", newGolangFunctor(isListFunc), typeBoolean, anyParams)
ctx.RegisterFunc("dec", &simpleFunctor{f: decFunc}, 1, 1) ctx.RegisterFunc("isDict", newGolangFunctor(isDictionaryFunc), typeBoolean, anyParams)
ctx.RegisterFunc("fract", &simpleFunctor{f: fractFunc}, 1, 2)
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() { func init() {

View File

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

View File

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

View File

@ -158,12 +158,26 @@ func readFileFunc(ctx ExprContext, name string, args []any) (result any, err err
} }
func ImportOsFuncs(ctx ExprContext) { func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("openFile", &simpleFunctor{f: openFileFunc}, 1, 1) ctx.RegisterFunc("openFile", newGolangFunctor(openFileFunc), typeHandle, []ExprFuncParam{
ctx.RegisterFunc("appendFile", &simpleFunctor{f: appendFileFunc}, 1, 1) newFuncParam(typeFilepath),
ctx.RegisterFunc("createFile", &simpleFunctor{f: createFileFunc}, 1, 1) })
ctx.RegisterFunc("writeFile", &simpleFunctor{f: writeFileFunc}, 1, -1) ctx.RegisterFunc("appendFile", newGolangFunctor(appendFileFunc), typeHandle, []ExprFuncParam{
ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2) newFuncParam(typeFilepath),
ctx.RegisterFunc("closeFile", &simpleFunctor{f: closeFileFunc}, 1, 1) })
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() { func init() {

View File

@ -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) { func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
// if len(args) < 1 { // if len(args) < 1 {
// return nil, errMissingRequiredParameter(name, paramSeparator) // return nil, errMissingRequiredParameter(name, paramSeparator)
// } // }
if sep, ok := args[0].(string); ok { if sep, ok := args[0].(string); ok {
if len(args) == 1 { if len(args) == 1 {
result = "" result = ""
@ -62,9 +62,6 @@ func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error
var source string var source string
var ok bool var ok bool
// if len(args) < 1 {
// return nil, errMissingRequiredParameter(name, paramSource)
// }
if source, ok = args[0].(string); !ok { if source, ok = args[0].(string); !ok {
return nil, errWrongParamType(name, paramSource, typeString, args[0]) 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 source string
var ok bool var ok bool
// if len(args) < 1 {
// return nil, errMissingRequiredParameter(name, paramSource)
// }
if source, ok = args[0].(string); !ok { if source, ok = args[0].(string); !ok {
return nil, errWrongParamType(name, paramSource, typeString, args[0]) 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 var ok bool
result = false result = false
// if len(args) < 1 {
// return result, errMissingRequiredParameter(name, paramSource)
// }
if source, ok = args[0].(string); !ok { if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0]) 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 var ok bool
result = false result = false
// if len(args) < 1 {
// return result, errMissingRequiredParameter(name, paramSource)
// }
if source, ok = args[0].(string); !ok { if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0]) 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 parts []string
var ok bool var ok bool
// if len(args) < 1 {
// return result, errMissingRequiredParameter(name, paramSource)
// }
if source, ok = args[0].(string); !ok { if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0]) 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 // Import above functions in the context
func ImportStringFuncs(ctx ExprContext) { func ImportStringFuncs(ctx ExprContext) {
ctx.RegisterFunc("joinStr", &simpleFunctor{f: joinStrFunc}, 1, -1) ctx.RegisterFunc("joinStr", newGolangFunctor(joinStrFunc), typeString, []ExprFuncParam{
ctx.RegisterFunc("subStr", &simpleFunctor{f: subStrFunc}, 1, -1) newFuncParam(paramSeparator),
ctx.RegisterFunc("splitStr", &simpleFunctor{f: splitStrFunc}, 2, -1) newFuncParamFlagDef(paramItem, pfOptional|pfRepeat, ""),
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("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. // 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}, /* 63 */ {`dec(true)`, float64(1), nil},
/* 64 */ {`dec(true")`, nil, errors.New("[1:11] missing string termination \"")}, /* 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 */ {`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()`, 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 */ {`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`)}, /* 68 */ {`builtin "string"; joinStr()`, nil, errors.New(`too few params -- expected 1 or more, got 0`)},
// /* 64 */ {`string(true)`, "true", nil}, /* 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", ".") t.Setenv("EXPR_PATH", ".")
//parserTest(t, "Func", inputs[54:55]) // parserTestSpec(t, "Func", inputs, 69)
parserTest(t, "Func", inputs) parserTest(t, "Func", inputs)
} }

232
function.go Normal file
View 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
}

View File

@ -6,7 +6,7 @@ package expr
import "path/filepath" import "path/filepath"
var globalCtx *SimpleFuncStore var globalCtx *SimpleStore
func ImportInContext(name string) (exists bool) { func ImportInContext(name string) (exists bool) {
var mod *module var mod *module
@ -50,5 +50,6 @@ func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool, owne
} }
func init() { func init() {
globalCtx = NewSimpleFuncStore() globalCtx = NewSimpleStore()
ImportBuiltinsFuncs(globalCtx)
} }

View File

@ -34,12 +34,15 @@ func EvalStringA(source string, args ...Arg) (result any, err error) {
} }
func EvalStringV(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 { for _, arg := range args {
if isFunc(arg.Value) { if isFunc(arg.Value) {
if f, ok := arg.Value.(FuncTemplate); ok { if f, ok := arg.Value.(FuncTemplate); ok {
functor := &simpleFunctor{f: f} functor := newGolangFunctor(f)
ctx.RegisterFunc(arg.Name, functor, 0, -1) // ctx.RegisterFunc(arg.Name, functor, 0, -1)
ctx.RegisterFunc(arg.Name, functor, typeAny, []ExprFuncParam{
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 0),
})
} else { } else {
err = fmt.Errorf("invalid function specification: %q", arg.Name) err = fmt.Errorf("invalid function specification: %q", arg.Name)
} }

View File

@ -52,7 +52,7 @@ func TestEvalStringA(t *testing.T) {
func TestEvalString(t *testing.T) { func TestEvalString(t *testing.T) {
ctx := NewSimpleVarStore() ctx := NewSimpleStore()
ctx.SetVar("a", uint8(1)) ctx.SetVar("a", uint8(1))
ctx.SetVar("b", int8(2)) ctx.SetVar("b", int8(2))
ctx.SetVar("f", 2.0) ctx.SetVar("f", 2.0)

View File

@ -60,7 +60,7 @@ func TestListParser(t *testing.T) {
var gotResult any var gotResult any
var gotErr error var gotErr error
ctx := NewSimpleFuncStore() ctx := NewSimpleStore()
// ctx.SetVar("var1", int64(123)) // ctx.SetVar("var1", int64(123))
// ctx.SetVar("var2", "abc") // ctx.SetVar("var2", "abc")
ImportMathFuncs(ctx) ImportMathFuncs(ctx)

View File

@ -31,7 +31,8 @@ func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) {
err = errTooMuchParams(info.MaxArgs(), len(params)) err = errTooMuchParams(info.MaxArgs(), len(params))
} }
if err == nil && owner != ctx { 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 { } else {
err = fmt.Errorf("unknown function %s()", name) err = fmt.Errorf("unknown function %s()", name)
@ -75,27 +76,30 @@ func newFuncDefTerm(tk *Token, args []*term) *term {
// -------- eval func def // -------- eval func def
// TODO // TODO
type funcDefFunctor struct { // type funcDefFunctor struct {
params []string // params []string
expr Expr // expr Expr
} // }
func (functor *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) { // func (funcDef *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
for i, p := range functor.params { // for i, p := range funcDef.params {
if i < len(args) { // if i < len(args) {
arg := args[i] // arg := args[i]
if functor, ok := arg.(Functor); ok { // if functor, ok := arg.(Functor); ok {
ctx.RegisterFunc(p, functor, 0, -1) // // ctx.RegisterFunc(p, functor, 0, -1)
} else { // ctx.RegisterFunc2(p, functor, typeAny, []ExprFuncParam{
ctx.UnsafeSetVar(p, arg) // newFuncParam(paramValue),
} // })
} else { // } else {
ctx.UnsafeSetVar(p, nil) // ctx.UnsafeSetVar(p, arg)
} // }
} // } else {
result, err = functor.expr.eval(ctx, false) // ctx.UnsafeSetVar(p, nil)
return // }
} // }
// result, err = funcDef.expr.eval(ctx, false)
// return
// }
func evalFuncDef(ctx ExprContext, self *term) (v any, err error) { func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
bodySpec := self.value() bodySpec := self.value()
@ -104,10 +108,11 @@ func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
for _, param := range self.children { for _, param := range self.children {
paramList = append(paramList, param.source()) paramList = append(paramList, param.source())
} }
v = &funcDefFunctor{ v = newExprFunctor(expr, paramList)
params: paramList, // v = &funcDefFunctor{
expr: expr, // params: paramList,
} // expr: expr,
// }
} else { } else {
err = errors.New("invalid function definition: the body specification must be an expression") err = errors.New("invalid function definition: the body specification must be an expression")
} }

View File

@ -74,7 +74,8 @@ func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map
ds = make(map[string]Functor) ds = make(map[string]Functor)
for keyAny, item := range *dictAny { for keyAny, item := range *dictAny {
if key, ok := keyAny.(string); ok { 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 ds[key] = functor
if index := slices.Index(requiredFields, key); index >= 0 { if index := slices.Index(requiredFields, key); index >= 0 {
foundFields |= 1 << index foundFields |= 1 << index

View File

@ -31,18 +31,21 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
if v, err = rightChild.compute(ctx); err == nil { if v, err = rightChild.compute(ctx); err == nil {
if functor, ok := v.(Functor); ok { if functor, ok := v.(Functor); ok {
var minArgs, maxArgs int = 0, -1
funcName := rightChild.source() funcName := rightChild.source()
if info, exists := ctx.GetFuncInfo(funcName); exists { if info, exists, _ := GetFuncInfo(ctx, funcName); exists {
minArgs = info.MinArgs() // ctx.RegisterFuncInfo(info)
maxArgs = info.MaxArgs() ctx.RegisterFunc(leftTerm.source(), info.Functor(), info.ReturnType(), info.Params())
} else if funcDef, ok := functor.(*funcDefFunctor); ok { } else if funcDef, ok := functor.(*exprFunctor); ok {
l := len(funcDef.params) paramSpecs := ForAll(funcDef.params, newFuncParam)
minArgs = l // paramCount := len(funcDef.params)
maxArgs = l // 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 { } else {
ctx.UnsafeSetVar(leftTerm.source(), v) ctx.UnsafeSetVar(leftTerm.source(), v)
} }

View File

@ -66,7 +66,10 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
v = leftValue v = leftValue
} else if rightValue, err = self.children[1].compute(ctx); err == nil { } else if rightValue, err = self.children[1].compute(ctx); err == nil {
if functor, ok := rightValue.(Functor); ok { 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 { } else {
v = rightValue v = rightValue
ctx.UnsafeSetVar(leftTerm.source(), 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 childValue any
var sourceCtx ExprContext var sourceCtx ExprContext
if len(self.children) == 0 { if self.children == nil || len(self.children) == 0 {
sourceCtx = ctx 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 { } else if childValue, err = self.evalPrefix(ctx); err == nil {
if dc, ok := childValue.(*dataCursor); ok { if dc, ok := childValue.(*dataCursor); ok {
sourceCtx = dc.ctx sourceCtx = dc.ctx
} }
} else {
return
} }
if sourceCtx != nil { if sourceCtx != nil {
if formatter, ok := ctx.(Formatter); ok { if formatter, ok := sourceCtx.(Formatter); ok {
v = formatter.ToString(0) v = formatter.ToString(0)
} else { } else {
keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' }) 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", ".") // t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, "General", inputs, 102)
parserTest(t, "General", inputs) 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) { func parserTest(t *testing.T, section string, inputs []inputType) {
succeeded := 0 succeeded := 0
failed := 0 failed := 0
for i, input := range inputs { for i, input := range inputs {
var expr Expr good := doTest(t, section, &input, i+1)
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
}
}
if good { if good {
succeeded++ succeeded++
} else { } 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) 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) { func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
if wantErr == nil { if wantErr == nil {
t.Logf("[+]%s nr %3d -- %q --> %v", section, n, source, wantResult) t.Logf("[+]%s nr %3d -- %q --> %v", section, n, source, wantResult)

View File

@ -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
View 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
View 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
}

View File

@ -72,7 +72,12 @@ func (ctx *SimpleVarStore) Call(name string, args []any) (result any, err error)
return 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) { func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {

View File

@ -209,3 +209,11 @@ func toInt(value any, description string) (i int, err error) {
} }
return return
} }
func ForAll[T, V any](ts []T, fn func(T) V) []V {
result := make([]V, len(ts))
for i, t := range ts {
result[i] = fn(t)
}
return result
}