Compare commits

...

42 Commits

Author SHA1 Message Date
camoroso dceb31f542 CallFunction() has been replaced by three new functions:
CallFunctionByTerm(), CallFunctionByArgs() and CallFunctionByParams()
2024-08-02 06:39:33 +02:00
camoroso 075b0b5691 RegisterFunc() also returns the funcInfo object 2024-08-01 00:09:49 +02:00
camoroso 3c51b8d2ee Changed some function names and param types 2024-07-31 09:11:57 +02:00
camoroso a46753f453 Function buildActualParams moved from data-cursor.go ro function.go 2024-07-31 09:08:58 +02:00
camoroso 9070b5c9cc The function parameter model has been modified to support the passing of named parameters 2024-07-28 18:49:08 +02:00
camoroso ab06702e5e operator-post-inc.go: new post int decrement operator '--' 2024-07-24 06:39:35 +02:00
camoroso ffe1fa3aac op-assign expansion now end at ']' and '}' too 2024-07-24 06:37:57 +02:00
camoroso 1a1a475dd8 Added support to op-assign operators like '+='.
This feature is implemented by expansion, not by dedicated operators, e.g. a*=2+x is exapanded as a=a*(2+x)
2024-07-23 15:35:57 +02:00
camoroso 463e3634ba scanner.go: New function UnreadToken().
Currently it only supports one staging level.
2024-07-23 15:32:25 +02:00
camoroso 5f8ca47ef0 term.go: New function Clone() 2024-07-23 15:27:50 +02:00
camoroso 837b887490 token.go: New functions Clone() and IsOneOfA() 2024-07-23 15:27:36 +02:00
camoroso c76e1d3c8e symbol.go: New symbol '*=' 2024-07-23 15:24:54 +02:00
camoroso 315f5b22d3 data-cursor.go: the inizialization of the current item is done in the Next() method.
This allows the application of the filter and map operator to the first item too.
2024-07-23 05:46:37 +02:00
camoroso dfae593e86 operand-iterator.go: removed commented code 2024-07-21 16:35:13 +02:00
camoroso d572f3a129 Iterator: new function Count() 2024-07-21 16:34:52 +02:00
camoroso c461fd138e Iterator defined by data-source now only requires one method: next() 2024-07-21 05:45:22 +02:00
camoroso 6ecbe2feb1 Fixed type (expcted -> expected) 2024-07-21 05:43:55 +02:00
camoroso 80d3c6ec7d parser.go: Fixed an old bug that did not allow the parser to skip comment tokens 2024-07-21 05:37:34 +02:00
camoroso e09806c716 %q replaced by %s in some error messages 2024-07-21 05:33:06 +02:00
camoroso 1a772597cb removed/commented unused code 2024-07-19 17:03:03 +02:00
camoroso 33b3e1fc29 New function for searching and importing plugin 2024-07-19 15:37:00 +02:00
camoroso 4e3f5cfbc6 import-utils.go: Paths are now expanded with respect to env-vars and shell ~ 2024-07-19 15:33:15 +02:00
camoroso e35d4e3f70 utils.go: added function ExpandPath 2024-07-19 15:30:26 +02:00
camoroso b4529499d6 iterator.go: exported some const identifier 2024-07-18 07:27:02 +02:00
camoroso 7745dc24e2 dict-type.go: exported NewDataCursor 2024-07-18 07:25:51 +02:00
camoroso be25385d02 builtin-iterator: changed status variable from 'it_status' to 'status' 2024-07-15 06:59:13 +02:00
camoroso 79889cd8e1 New builtin module 'iterator' 2024-07-14 16:53:32 +02:00
camoroso 234759158c scanner: disabled prefix operator '()' 2024-07-13 18:10:04 +02:00
camoroso 00fda29606 operator-iter-value.go: prefix operator () used to get the current value of an iterator replaced by ^ 2024-07-13 17:17:16 +02:00
camoroso 2a2840bdf2 ListIterator now implements next and current command (e.g it.next) 2024-07-13 17:15:53 +02:00
camoroso 06373f5126 Impemented Typer interface 2024-07-13 17:14:25 +02:00
camoroso e69dad5fb5 Rename priority label priCoalesce as priDefault 2024-07-13 17:13:16 +02:00
camoroso 7459057df9 Expr embeds Typer and ast implements it 2024-07-13 17:11:39 +02:00
camoroso 176969c956 New iterator tests 2024-07-13 16:19:04 +02:00
camoroso cde2efacf1 Test on iterator filter and map 2024-07-13 15:30:04 +02:00
camoroso d7a7b3218c Merge branch 'main' into enhance_iterators 2024-07-13 09:08:16 +02:00
camoroso be3bb12f28 operator-unset.go: fixed function removal 2024-07-13 09:07:33 +02:00
camoroso 032916d4fa New operator unset to delete variables and functions from current context 2024-07-13 09:01:59 +02:00
camoroso f3cc0cc7ad operator-include.go: Fixed inclusion of a list of files.
Now returns the value of the last evaluated expression.
2024-07-13 09:00:53 +02:00
camoroso 905337f963 ExprContext: new functions VarCount(), DeleteVar(), FuncCount(), DeleteFunc() 2024-07-13 08:59:15 +02:00
camoroso 9a95a837f6 data-cursor.go: add item mapping support 2024-07-13 06:44:00 +02:00
camoroso 8547248ea2 data-cursor.go: fixed filter 2024-07-13 00:12:08 +02:00
60 changed files with 1250 additions and 480 deletions
+10 -6
View File
@@ -19,6 +19,10 @@ func NewAst() *ast {
return &ast{} return &ast{}
} }
func (expr *ast) TypeName() string {
return "Expression"
}
func (expr *ast) ToForest() { func (expr *ast) ToForest() {
if expr.root != nil { if expr.root != nil {
if expr.forest == nil { if expr.forest == nil {
@@ -41,19 +45,19 @@ func (expr *ast) String() string {
func (expr *ast) addTokens(tokens ...*Token) (err error) { func (expr *ast) addTokens(tokens ...*Token) (err error) {
for _, tk := range tokens { for _, tk := range tokens {
if err = expr.addToken(tk); err != nil { if _, err = expr.addToken(tk); err != nil {
break break
} }
} }
return return
} }
func (expr *ast) addToken(tk *Token) (err error) { // func (expr *ast) addToken(tk *Token) (err error) {
_, err = expr.addToken2(tk) // _, err = expr.addToken2(tk)
return // return
} // }
func (expr *ast) addToken2(tk *Token) (t *term, err error) { func (expr *ast) addToken(tk *Token) (t *term, err error) {
if t = newTerm(tk); t != nil { if t = newTerm(tk); t != nil {
err = expr.addTerm(t) err = expr.addTerm(t)
} else { } else {
+22 -19
View File
@@ -12,35 +12,30 @@ type exprFunctor struct {
defCtx ExprContext defCtx ExprContext
} }
// func newExprFunctor(e Expr, params []string, ctx ExprContext) *exprFunctor { func (functor *exprFunctor) GetParams() (params []ExprFuncParam) {
// return &exprFunctor{expr: e, params: params, defCtx: ctx} return functor.params
// } }
func newExprFunctor(e Expr, params []ExprFuncParam, ctx ExprContext) *exprFunctor { func newExprFunctor(e Expr, params []ExprFuncParam, ctx ExprContext) *exprFunctor {
var defCtx ExprContext var defCtx ExprContext
if ctx != nil { if ctx != nil {
// if ctx.GetParent() != nil { defCtx = ctx
// defCtx = ctx.Clone()
// defCtx.SetParent(ctx)
// } else {
defCtx = ctx
// }
} }
return &exprFunctor{expr: e, params: params, defCtx: defCtx} return &exprFunctor{expr: e, params: params, defCtx: defCtx}
} }
func (functor *exprFunctor) TypeName() string {
return "ExprFunctor"
}
func (functor *exprFunctor) GetDefinitionContext() ExprContext { func (functor *exprFunctor) GetDefinitionContext() ExprContext {
return functor.defCtx return functor.defCtx
} }
func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) { func (functor *exprFunctor) InvokeNamed(ctx ExprContext, name string, args map[string]any) (result any, err error) {
// if functor.defCtx != nil { var missing []string
// ctx.Merge(functor.defCtx) for _, p := range functor.params {
// } if arg, exists := args[p.Name()]; exists {
for i, p := range functor.params {
if i < len(args) {
arg := args[i]
if funcArg, ok := arg.(Functor); ok { if funcArg, ok := arg.(Functor); ok {
paramSpecs := funcArg.GetParams() paramSpecs := funcArg.GetParams()
ctx.RegisterFunc(p.Name(), funcArg, TypeAny, paramSpecs) ctx.RegisterFunc(p.Name(), funcArg, TypeAny, paramSpecs)
@@ -48,9 +43,17 @@ func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (re
ctx.UnsafeSetVar(p.Name(), arg) ctx.UnsafeSetVar(p.Name(), arg)
} }
} else { } else {
ctx.UnsafeSetVar(p.Name(), nil) if missing == nil {
missing = make([]string, 0, 1)
}
missing = append(missing, p.Name())
// ctx.UnsafeSetVar(p.Name(), nil)
} }
} }
result, err = functor.expr.Eval(ctx) if missing != nil {
err = ErrMissingParams(name, missing)
} else {
result, err = functor.expr.Eval(ctx)
}
return return
} }
+5 -1
View File
@@ -14,6 +14,10 @@ func NewGolangFunctor(f FuncTemplate) *golangFunctor {
return &golangFunctor{f: f} return &golangFunctor{f: f}
} }
func (functor *golangFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) { func (functor *golangFunctor) TypeName() string {
return "GoFunctor"
}
func (functor *golangFunctor) InvokeNamed(ctx ExprContext, name string, args map[string]any) (result any, err error) {
return functor.f(ctx, name, args) return functor.f(ctx, name, args)
} }
+40 -36
View File
@@ -10,53 +10,57 @@ import (
"strconv" "strconv"
) )
func isNilFunc(ctx ExprContext, name string, args []any) (result any, err error) { const (
result = args[0] == nil ParamDenominator = "denominator"
)
func isNilFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
result = args[ParamValue] == nil
return return
} }
func isIntFunc(ctx ExprContext, name string, args []any) (result any, err error) { func isIntFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
result = IsInteger(args[0]) result = IsInteger(args[ParamValue])
return return
} }
func isFloatFunc(ctx ExprContext, name string, args []any) (result any, err error) { func isFloatFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
result = IsFloat(args[0]) result = IsFloat(args[ParamValue])
return return
} }
func isBoolFunc(ctx ExprContext, name string, args []any) (result any, err error) { func isBoolFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
result = IsBool(args[0]) result = IsBool(args[ParamValue])
return return
} }
func isStringFunc(ctx ExprContext, name string, args []any) (result any, err error) { func isStringFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
result = IsString(args[0]) result = IsString(args[ParamValue])
return return
} }
func isFractionFunc(ctx ExprContext, name string, args []any) (result any, err error) { func isFractionFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
result = IsFract(args[0]) result = IsFract(args[ParamValue])
return return
} }
func isRationalFunc(ctx ExprContext, name string, args []any) (result any, err error) { func isRationalFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
result = IsRational(args[0]) result = IsRational(args[ParamValue])
return return
} }
func isListFunc(ctx ExprContext, name string, args []any) (result any, err error) { func isListFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
result = IsList(args[0]) result = IsList(args[ParamValue])
return return
} }
func isDictionaryFunc(ctx ExprContext, name string, args []any) (result any, err error) { func isDictionaryFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
result = IsDict(args[0]) result = IsDict(args[ParamValue])
return return
} }
func boolFunc(ctx ExprContext, name string, args []any) (result any, err error) { func boolFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
switch v := args[0].(type) { switch v := args[ParamValue].(type) {
case int64: case int64:
result = (v != 0) result = (v != 0)
case *FractionType: case *FractionType:
@@ -73,8 +77,8 @@ func boolFunc(ctx ExprContext, name string, args []any) (result any, err error)
return return
} }
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) { func intFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
switch v := args[0].(type) { switch v := args[ParamValue].(type) {
case int64: case int64:
result = v result = v
case float64: case float64:
@@ -96,8 +100,8 @@ func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
return return
} }
func decFunc(ctx ExprContext, name string, args []any) (result any, err error) { func decFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
switch v := args[0].(type) { switch v := args[ParamValue].(type) {
case int64: case int64:
result = float64(v) result = float64(v)
case float64: case float64:
@@ -121,8 +125,8 @@ func decFunc(ctx ExprContext, name string, args []any) (result any, err error) {
return return
} }
func stringFunc(ctx ExprContext, name string, args []any) (result any, err error) { func stringFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
switch v := args[0].(type) { switch v := args[ParamValue].(type) {
case int64: case int64:
result = strconv.FormatInt(v, 10) result = strconv.FormatInt(v, 10)
case float64: case float64:
@@ -147,18 +151,18 @@ func stringFunc(ctx ExprContext, name string, args []any) (result any, err error
return return
} }
func fractFunc(ctx ExprContext, name string, args []any) (result any, err error) { func fractFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
switch v := args[0].(type) { switch v := args[ParamValue].(type) {
case int64: case int64:
var den int64 = 1 var den int64 = 1
if len(args) > 1 {
var ok bool var ok bool
if den, ok = args[1].(int64); !ok { if den, ok = args[ParamDenominator].(int64); !ok {
err = ErrExpectedGot(name, "integer", args[1]) err = ErrExpectedGot(name, "integer", args[ParamDenominator])
} else if den == 0 { } else if den == 0 {
err = ErrFuncDivisionByZero(name) err = ErrFuncDivisionByZero(name)
}
} }
if err == nil { if err == nil {
result = newFraction(v, den) result = newFraction(v, den)
} }
@@ -205,7 +209,7 @@ func ImportBuiltinsFuncs(ctx ExprContext) {
ctx.RegisterFunc("string", NewGolangFunctor(stringFunc), TypeString, anyParams) ctx.RegisterFunc("string", NewGolangFunctor(stringFunc), TypeString, anyParams)
ctx.RegisterFunc("fract", NewGolangFunctor(fractFunc), TypeFraction, []ExprFuncParam{ ctx.RegisterFunc("fract", NewGolangFunctor(fractFunc), TypeFraction, []ExprFuncParam{
NewFuncParam(ParamValue), NewFuncParam(ParamValue),
NewFuncParamFlagDef("denominator", PfDefault, 1), NewFuncParamFlagDef(ParamDenominator, PfDefault, int64(1)),
}) })
} }
+14 -8
View File
@@ -21,19 +21,25 @@ func getStdout(ctx ExprContext) io.Writer {
return w return w
} }
func printFunc(ctx ExprContext, name string, args []any) (result any, err error) { func printFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var n int var n int = 0
if n, err = fmt.Fprint(getStdout(ctx), args...); err == nil { if v, exists := args[ParamItem]; exists && v != nil {
result = int64(n) argv := v.([]any)
n, err = fmt.Fprint(getStdout(ctx), argv...)
} }
result = int64(n)
return return
} }
func printLnFunc(ctx ExprContext, name string, args []any) (result any, err error) { func printLnFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var n int var n int = 0
if n, err = fmt.Fprintln(getStdout(ctx), args...); err == nil { if v, exists := args[ParamItem]; exists && v != nil {
result = int64(n) argv := v.([]any)
n, err = fmt.Fprintln(getStdout(ctx), argv...)
} else {
n, err = fmt.Fprintln(getStdout(ctx))
} }
result = int64(n)
return return
} }
+7 -4
View File
@@ -9,18 +9,21 @@ import (
"os" "os"
) )
func importFunc(ctx ExprContext, name string, args []any) (result any, err error) { func importFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
return importGeneral(ctx, name, args) return importGeneral(ctx, name, args)
} }
func importAllFunc(ctx ExprContext, name string, args []any) (result any, err error) { func importAllFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
CtrlEnable(ctx, control_export_all) CtrlEnable(ctx, control_export_all)
return importGeneral(ctx, name, args) return importGeneral(ctx, name, args)
} }
func importGeneral(ctx ExprContext, name string, args []any) (result any, err error) { func importGeneral(ctx ExprContext, name string, args map[string]any) (result any, err error) {
dirList := buildSearchDirList("sources", ENV_EXPR_SOURCE_PATH) dirList := buildSearchDirList("sources", ENV_EXPR_SOURCE_PATH)
result, err = doImport(ctx, name, dirList, NewArrayIterator(args)) if v, exists := args[ParamFilepath]; exists && v != nil {
argv := v.([]any)
result, err = doImport(ctx, name, dirList, NewArrayIterator(argv))
}
return return
} }
+104
View File
@@ -0,0 +1,104 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-iterator.go
package expr
import (
"fmt"
"io"
)
const (
iterParamOperator = "operator"
iterParamVars = "vars"
iterVarStatus = "status"
)
func parseRunArgs(localCtx ExprContext, args map[string]any) (it Iterator, op Functor, err error) {
var ok bool
if it, ok = args[ParamIterator].(Iterator); !ok {
err = fmt.Errorf("paramter %q must be an iterator, passed %v [%s]", ParamIterator, args[ParamIterator], TypeName(args[ParamIterator]))
return
}
if op, ok = args[iterParamOperator].(Functor); !ok && args[iterParamOperator] != nil {
err = fmt.Errorf("paramter %q must be a function, passed %v [%s]", iterParamOperator, args[iterParamOperator], TypeName(args[iterParamOperator]))
return
}
var vars *DictType
if vars, ok = args[iterParamVars].(*DictType); !ok && args[iterParamVars] != nil {
err = fmt.Errorf("paramter %q must be a dictionary, passed %v [%s]", iterParamVars, args[iterParamVars], TypeName(args[iterParamVars]))
return
}
if vars != nil {
for key, value := range *vars {
var varName string
if varName, ok = key.(string); ok {
localCtx.UnsafeSetVar(varName, value)
}
}
}
return
}
func runFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var it Iterator
var ok bool
var op Functor
var v any
var usingDefaultOp = false
var params map[string]any
var item any
localCtx := ctx.Clone()
localCtx.UnsafeSetVar(iterVarStatus, nil)
if it, op, err = parseRunArgs(localCtx, args); err != nil {
return
} else if op == nil {
op = NewGolangFunctor(printLnFunc)
usingDefaultOp = true
}
for item, err = it.Next(); err == nil; item, err = it.Next() {
if usingDefaultOp {
params = map[string]any{ParamItem: []any{item}}
} else {
params = map[string]any{ParamIndex: it.Index(), ParamItem: item}
}
if v, err = op.InvokeNamed(localCtx, iterParamOperator, params); err != nil {
break
} else {
var success bool
if success, ok = ToBool(v); !success || !ok {
break
}
}
}
if err == io.EOF {
err = nil
}
if err == nil {
result, _ = localCtx.GetVar(iterVarStatus)
}
return
}
func ImportIterFuncs(ctx ExprContext) {
ctx.RegisterFunc("run", NewGolangFunctor(runFunc), TypeAny, []ExprFuncParam{
NewFuncParam(ParamIterator),
NewFuncParamFlag(iterParamOperator, PfOptional),
NewFuncParamFlag(iterParamVars, PfOptional),
})
}
func init() {
RegisterBuiltinModule("iterator", ImportIterFuncs, "Iterator helper functions")
}
+12 -10
View File
@@ -34,8 +34,8 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
if v, err = doAdd(ctx, name, subIter, count, level); err != nil { if v, err = doAdd(ctx, name, subIter, count, level); err != nil {
break break
} }
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(cleanName) { if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(CleanName) {
if _, err = extIter.CallOperation(cleanName, nil); err != nil { if _, err = extIter.CallOperation(CleanName, nil); err != nil {
return return
} }
} }
@@ -86,8 +86,9 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
return return
} }
func addFunc(ctx ExprContext, name string, args []any) (result any, err error) { func addFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
result, err = doAdd(ctx, name, NewArrayIterator(args), 0, -1) argv := args[ParamValue].([]any)
result, err = doAdd(ctx, name, NewArrayIterator(argv), 0, -1)
return return
} }
@@ -107,8 +108,8 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
if v, err = doMul(ctx, name, subIter, count, level); err != nil { if v, err = doMul(ctx, name, subIter, count, level); err != nil {
break break
} }
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(cleanName) { if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(CleanName) {
if _, err = extIter.CallOperation(cleanName, nil); err != nil { if _, err = extIter.CallOperation(CleanName, nil); err != nil {
return return
} }
} }
@@ -161,17 +162,18 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
return return
} }
func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) { func mulFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
result, err = doMul(ctx, name, NewArrayIterator(args), 0, -1) argv := args[ParamValue].([]any)
result, err = doMul(ctx, name, NewArrayIterator(argv), 0, -1)
return return
} }
func ImportMathFuncs(ctx ExprContext) { func ImportMathFuncs(ctx ExprContext) {
ctx.RegisterFunc("add", &golangFunctor{f: addFunc}, TypeNumber, []ExprFuncParam{ ctx.RegisterFunc("add", NewGolangFunctor(addFunc), TypeNumber, []ExprFuncParam{
NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, int64(0)), NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, int64(0)),
}) })
ctx.RegisterFunc("mul", &golangFunctor{f: mulFunc}, TypeNumber, []ExprFuncParam{ ctx.RegisterFunc("mul", NewGolangFunctor(mulFunc), TypeNumber, []ExprFuncParam{
NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, int64(1)), NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, int64(1)),
}) })
} }
+36 -29
View File
@@ -11,6 +11,10 @@ import (
"os" "os"
) )
const (
osLimitCh = "limitCh"
)
type osHandle interface { type osHandle interface {
getFile() *os.File getFile() *os.File
} }
@@ -61,8 +65,8 @@ func errInvalidFileHandle(funcName string, v any) error {
} }
} }
func createFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func createFileFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
if filePath, ok := args[0].(string); ok && len(filePath) > 0 { if filePath, ok := args[ParamFilepath].(string); ok && len(filePath) > 0 {
var fh *os.File var fh *os.File
if fh, err = os.Create(filePath); err == nil { if fh, err = os.Create(filePath); err == nil {
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)} result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
@@ -73,8 +77,8 @@ func createFileFunc(ctx ExprContext, name string, args []any) (result any, err e
return return
} }
func openFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func openFileFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
if filePath, ok := args[0].(string); ok && len(filePath) > 0 { if filePath, ok := args[ParamFilepath].(string); ok && len(filePath) > 0 {
var fh *os.File var fh *os.File
if fh, err = os.Open(filePath); err == nil { if fh, err = os.Open(filePath); err == nil {
result = &osReader{fh: fh, reader: bufio.NewReader(fh)} result = &osReader{fh: fh, reader: bufio.NewReader(fh)}
@@ -85,8 +89,8 @@ func openFileFunc(ctx ExprContext, name string, args []any) (result any, err err
return return
} }
func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func appendFileFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
if filePath, ok := args[0].(string); ok && len(filePath) > 0 { if filePath, ok := args[ParamFilepath].(string); ok && len(filePath) > 0 {
var fh *os.File var fh *os.File
if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil { if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil {
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)} result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
@@ -97,13 +101,13 @@ func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err e
return return
} }
func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func closeFileFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var handle osHandle var handle osHandle
var invalidFileHandle any var invalidFileHandle any
var ok bool var ok bool
if handle, ok = args[0].(osHandle); !ok { if handle, ok = args[ParamHandle].(osHandle); !ok {
invalidFileHandle = args[0] invalidFileHandle = args[ParamHandle]
} }
if handle != nil { if handle != nil {
@@ -124,18 +128,21 @@ func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err er
return return
} }
func fileWriteTextFunc(ctx ExprContext, name string, args []any) (result any, err error) { func fileWriteTextFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var handle osHandle var handle osHandle
var invalidFileHandle any var invalidFileHandle any
var ok bool var ok bool
if handle, ok = args[0].(osHandle); !ok { if handle, ok = args[ParamHandle].(osHandle); !ok {
invalidFileHandle = args[0] invalidFileHandle = args[ParamHandle]
} }
if handle != nil { if handle != nil {
if w, ok := handle.(*osWriter); ok { if w, ok := handle.(*osWriter); ok {
result, err = fmt.Fprint(w.writer, args[1:]...) if v, exists := args[ParamItem]; exists {
argv := v.([]any)
result, err = fmt.Fprint(w.writer, argv...)
}
} else { } else {
invalidFileHandle = handle invalidFileHandle = handle
} }
@@ -147,21 +154,21 @@ func fileWriteTextFunc(ctx ExprContext, name string, args []any) (result any, er
return return
} }
func fileReadTextFunc(ctx ExprContext, name string, args []any) (result any, err error) { func fileReadTextFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var handle osHandle var handle osHandle
var invalidFileHandle any var invalidFileHandle any
var ok bool var ok bool
result = nil result = nil
if handle, ok = args[0].(osHandle); !ok || args[0] == nil { if handle, ok = args[ParamHandle].(osHandle); !ok || args[ParamHandle] == nil {
invalidFileHandle = args[0] invalidFileHandle = args[ParamHandle]
} }
if handle != nil { if handle != nil {
if r, ok := handle.(*osReader); ok { if r, ok := handle.(*osReader); ok {
var limit byte = '\n' var limit byte = '\n'
var v string var v string
if s, ok := args[1].(string); ok && len(s) > 0 { if s, ok := args[osLimitCh].(string); ok && len(s) > 0 {
limit = s[0] limit = s[0]
} }
@@ -187,14 +194,14 @@ func fileReadTextFunc(ctx ExprContext, name string, args []any) (result any, err
return return
} }
func fileReadTextAllFunc(ctx ExprContext, name string, args []any) (result any, err error) { func fileReadTextAllFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var handle osHandle var handle osHandle
var invalidFileHandle any var invalidFileHandle any
var ok bool var ok bool
result = nil result = nil
if handle, ok = args[0].(osHandle); !ok || args[0] == nil { if handle, ok = args[ParamHandle].(osHandle); !ok || args[ParamHandle] == nil {
invalidFileHandle = args[0] invalidFileHandle = args[ParamHandle]
} }
if handle != nil { if handle != nil {
@@ -214,34 +221,34 @@ func fileReadTextAllFunc(ctx ExprContext, name string, args []any) (result any,
} }
func ImportOsFuncs(ctx ExprContext) { func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("fileOpen", NewGolangFunctor(openFileFunc), TypeHandle, []ExprFuncParam{ ctx.RegisterFunc("fileOpen", NewGolangFunctor(openFileFunc), TypeFileHandle, []ExprFuncParam{
NewFuncParam(ParamFilepath), NewFuncParam(ParamFilepath),
}) })
ctx.RegisterFunc("fileAppend", NewGolangFunctor(appendFileFunc), TypeHandle, []ExprFuncParam{ ctx.RegisterFunc("fileAppend", NewGolangFunctor(appendFileFunc), TypeFileHandle, []ExprFuncParam{
NewFuncParam(ParamFilepath), NewFuncParam(ParamFilepath),
}) })
ctx.RegisterFunc("fileCreate", NewGolangFunctor(createFileFunc), TypeHandle, []ExprFuncParam{ ctx.RegisterFunc("fileCreate", NewGolangFunctor(createFileFunc), TypeFileHandle, []ExprFuncParam{
NewFuncParam(ParamFilepath), NewFuncParam(ParamFilepath),
}) })
ctx.RegisterFunc("fileClose", NewGolangFunctor(closeFileFunc), TypeBoolean, []ExprFuncParam{ ctx.RegisterFunc("fileClose", NewGolangFunctor(closeFileFunc), TypeBoolean, []ExprFuncParam{
NewFuncParam(TypeHandle), NewFuncParam(ParamHandle),
}) })
ctx.RegisterFunc("fileWriteText", NewGolangFunctor(fileWriteTextFunc), TypeInt, []ExprFuncParam{ ctx.RegisterFunc("fileWriteText", NewGolangFunctor(fileWriteTextFunc), TypeInt, []ExprFuncParam{
NewFuncParam(TypeHandle), NewFuncParam(ParamHandle),
NewFuncParamFlagDef(TypeItem, PfDefault|PfRepeat, ""), NewFuncParamFlagDef(ParamItem, PfDefault|PfRepeat, ""),
}) })
ctx.RegisterFunc("fileReadText", NewGolangFunctor(fileReadTextFunc), TypeString, []ExprFuncParam{ ctx.RegisterFunc("fileReadText", NewGolangFunctor(fileReadTextFunc), TypeString, []ExprFuncParam{
NewFuncParam(TypeHandle), NewFuncParam(ParamHandle),
NewFuncParamFlagDef("limitCh", PfDefault, "\n"), NewFuncParamFlagDef(osLimitCh, PfDefault, "\n"),
}) })
ctx.RegisterFunc("fileReadTextAll", NewGolangFunctor(fileReadTextAllFunc), TypeString, []ExprFuncParam{ ctx.RegisterFunc("fileReadTextAll", NewGolangFunctor(fileReadTextAllFunc), TypeString, []ExprFuncParam{
NewFuncParam(TypeHandle), NewFuncParam(ParamHandle),
}) })
} }
+77 -55
View File
@@ -10,6 +10,10 @@ import (
"strings" "strings"
) )
const (
strParamOther = "other"
)
// --- Start of function definitions // --- Start of function definitions
func doJoinStr(funcName string, sep string, it Iterator) (result any, err error) { func doJoinStr(funcName string, sep string, it Iterator) (result any, err error) {
var sb strings.Builder var sb strings.Builder
@@ -32,45 +36,45 @@ func doJoinStr(funcName string, sep string, it Iterator) (result any, err error)
return return
} }
func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err error) { func joinStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
// if len(args) < 1 { if sep, ok := args[ParamSeparator].(string); ok {
// return nil, errMissingRequiredParameter(name, paramSeparator) if v, exists := args[ParamItem]; exists {
// } argv := v.([]any)
if sep, ok := args[0].(string); ok { if len(argv) == 1 {
if len(args) == 1 { if ls, ok := argv[0].(*ListType); ok {
result = "" result, err = doJoinStr(name, sep, NewListIterator(ls, nil))
} else if len(args) == 2 { } else if it, ok := argv[0].(Iterator); ok {
if ls, ok := args[1].(*ListType); ok { result, err = doJoinStr(name, sep, it)
result, err = doJoinStr(name, sep, NewListIterator(ls, nil)) } else if s, ok := argv[0].(string); ok {
} else if it, ok := args[1].(Iterator); ok { result = s
result, err = doJoinStr(name, sep, it) } else {
err = ErrInvalidParameterValue(name, ParamItem, v)
}
} else { } else {
err = ErrInvalidParameterValue(name, ParamParts, args[1]) result, err = doJoinStr(name, sep, NewArrayIterator(argv))
} }
} else {
result, err = doJoinStr(name, sep, NewArrayIterator(args[1:]))
} }
} else { } else {
err = ErrWrongParamType(name, ParamSeparator, TypeString, args[0]) err = ErrWrongParamType(name, ParamSeparator, TypeString, args[ParamSeparator])
} }
return return
} }
func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error) { func subStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var start = 0 var start = 0
var count = -1 var count = -1
var source string var source string
var ok bool var ok bool
if source, ok = args[0].(string); !ok { if source, ok = args[ParamSource].(string); !ok {
return nil, ErrWrongParamType(name, ParamSource, TypeString, args[0]) return nil, ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
} }
if start, err = ToGoInt(args[1], name+"()"); err != nil { if start, err = ToGoInt(args[ParamStart], name+"()"); err != nil {
return return
} }
if count, err = ToGoInt(args[2], name+"()"); err != nil { if count, err = ToGoInt(args[ParamCount], name+"()"); err != nil {
return return
} }
@@ -86,81 +90,99 @@ func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error
return return
} }
func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err error) { func trimStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var source string var source string
var ok bool var ok bool
if source, ok = args[0].(string); !ok { if source, ok = args[ParamSource].(string); !ok {
return nil, ErrWrongParamType(name, ParamSource, TypeString, args[0]) return nil, ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
} }
result = strings.TrimSpace(source) result = strings.TrimSpace(source)
return return
} }
func startsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err error) { func startsWithStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var source string var source, prefix string
var ok bool var ok bool
result = false result = false
if source, ok = args[0].(string); !ok { if source, ok = args[ParamSource].(string); !ok {
return result, ErrWrongParamType(name, ParamSource, TypeString, args[0]) return result, ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
} }
for i, targetSpec := range args[1:] {
if target, ok := targetSpec.(string); ok { if prefix, ok = args[ParamPrefix].(string); !ok {
if strings.HasPrefix(source, target) { return result, ErrWrongParamType(name, ParamPrefix, TypeString, args[ParamPrefix])
result = true }
if strings.HasPrefix(source, prefix) {
result = true
} else if v, exists := args[strParamOther]; exists {
argv := v.([]any)
for i, targetSpec := range argv {
if target, ok := targetSpec.(string); ok {
if strings.HasPrefix(source, target) {
result = true
break
}
} else {
err = fmt.Errorf("target item nr %d is %s, string expected", i+1, TypeName(targetSpec))
break break
} }
} else {
err = fmt.Errorf("target item nr %d is %T, expected string", i+1, targetSpec)
break
} }
} }
return return
} }
func endsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err error) { func endsWithStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var source string var source, suffix string
var ok bool var ok bool
result = false result = false
if source, ok = args[0].(string); !ok { if source, ok = args[ParamSource].(string); !ok {
return result, ErrWrongParamType(name, ParamSource, TypeString, args[0]) return result, ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
} }
for i, targetSpec := range args[1:] {
if target, ok := targetSpec.(string); ok { if suffix, ok = args[ParamSuffix].(string); !ok {
if strings.HasSuffix(source, target) { return result, ErrWrongParamType(name, ParamSuffix, TypeString, args[ParamSuffix])
result = true }
if strings.HasPrefix(source, suffix) {
result = true
} else if v, exists := args[strParamOther]; exists {
argv := v.([]any)
for i, targetSpec := range argv {
if target, ok := targetSpec.(string); ok {
if strings.HasSuffix(source, target) {
result = true
break
}
} else {
err = fmt.Errorf("target item nr %d is %s, string expected", i+1, TypeName(targetSpec))
break break
} }
} else {
err = fmt.Errorf("target item nr %d is %T, expected string", i+1, targetSpec)
break
} }
} }
return return
} }
func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err error) { func splitStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var source, sep string var source, sep string
var count int = -1 var count int = -1
var parts []string var parts []string
var ok bool var ok bool
if source, ok = args[0].(string); !ok { if source, ok = args[ParamSource].(string); !ok {
return result, ErrWrongParamType(name, ParamSource, TypeString, args[0]) return result, ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
} }
if sep, ok = args[1].(string); !ok { if sep, ok = args[ParamSeparator].(string); !ok {
return nil, fmt.Errorf("separator param must be string, got %T (%v)", args[1], args[1]) return nil, fmt.Errorf("separator param must be string, got %s (%v)", TypeName(args[ParamSeparator]), args[ParamSeparator])
} }
if count64, ok := args[2].(int64); ok { // TODO replace type assertion with toInt() if count64, ok := args[ParamCount].(int64); ok { // TODO replace type assertion with toInt()
count = int(count64) count = int(count64)
} else { } else {
return nil, fmt.Errorf("part count must be integer, got %T (%v)", args[2], args[2]) return nil, fmt.Errorf("part count must be integer, got %s (%v)", TypeName(args[ParamCount]), args[ParamCount])
} }
if count > 0 { if count > 0 {
@@ -206,13 +228,13 @@ func ImportStringFuncs(ctx ExprContext) {
ctx.RegisterFunc("strStartsWith", NewGolangFunctor(startsWithStrFunc), TypeBoolean, []ExprFuncParam{ ctx.RegisterFunc("strStartsWith", NewGolangFunctor(startsWithStrFunc), TypeBoolean, []ExprFuncParam{
NewFuncParam(ParamSource), NewFuncParam(ParamSource),
NewFuncParam(ParamPrefix), NewFuncParam(ParamPrefix),
NewFuncParamFlag("other "+ParamPrefix, PfRepeat), NewFuncParamFlag(strParamOther, PfRepeat),
}) })
ctx.RegisterFunc("strEndsWith", NewGolangFunctor(endsWithStrFunc), TypeBoolean, []ExprFuncParam{ ctx.RegisterFunc("strEndsWith", NewGolangFunctor(endsWithStrFunc), TypeBoolean, []ExprFuncParam{
NewFuncParam(ParamSource), NewFuncParam(ParamSource),
NewFuncParam(ParamSuffix), NewFuncParam(ParamSuffix),
NewFuncParamFlag("other "+ParamSuffix, PfRepeat), NewFuncParamFlag(strParamOther, PfRepeat),
}) })
} }
+11 -2
View File
@@ -6,8 +6,13 @@ package expr
import ( import (
"fmt" "fmt"
"strings"
) )
func ErrMissingParams(funcName string, missing []string) (err error) {
return fmt.Errorf("%s(): missing params -- %s", funcName, strings.Join(missing, ", "))
}
func ErrTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error) { func ErrTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error) {
if maxArgs < 0 { if maxArgs < 0 {
err = fmt.Errorf("%s(): too few params -- expected %d or more, got %d", funcName, minArgs, argCount) err = fmt.Errorf("%s(): too few params -- expected %d or more, got %d", funcName, minArgs, argCount)
@@ -17,8 +22,8 @@ func ErrTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error
return return
} }
func ErrTooMuchParams(funcName string, maxArgs, argCount int) (err error) { func ErrTooManyParams(funcName string, maxArgs, argCount int) (err error) {
err = fmt.Errorf("%s(): too much params -- expected %d, got %d", funcName, maxArgs, argCount) err = fmt.Errorf("%s(): too many params -- expected %d, got %d", funcName, maxArgs, argCount)
return return
} }
@@ -54,6 +59,10 @@ func ErrWrongParamType(funcName, paramName, paramType string, paramValue any) er
return fmt.Errorf("%s(): the %q parameter must be a %s, got a %s (%v)", funcName, paramName, paramType, TypeName(paramValue), paramValue) return fmt.Errorf("%s(): the %q parameter must be a %s, got a %s (%v)", funcName, paramName, paramType, TypeName(paramValue), paramValue)
} }
func ErrUnknownParam(funcName, paramName string) error {
return fmt.Errorf("%s(): unknown parameter %q", funcName, paramName)
}
// --- Operator errors // --- Operator errors
func ErrLeftOperandMustBeVariable(leftTerm, opTerm *term) error { func ErrLeftOperandMustBeVariable(leftTerm, opTerm *term) error {
+6 -1
View File
@@ -5,8 +5,10 @@
package expr package expr
const ( const (
ParamArgs = "args"
ParamCount = "count" ParamCount = "count"
ParamItem = "item" ParamItem = "item"
ParamIndex = "index"
ParamParts = "parts" ParamParts = "parts"
ParamSeparator = "separator" ParamSeparator = "separator"
ParamSource = "source" ParamSource = "source"
@@ -19,9 +21,12 @@ const (
ParamEllipsis = "..." ParamEllipsis = "..."
ParamFilepath = "filepath" ParamFilepath = "filepath"
ParamDirpath = "dirpath" ParamDirpath = "dirpath"
ParamHandle = "handle"
ParamResource = "resource"
ParamIterator = "iterator"
) )
// to be moved in its own source file // to be moved in its own source file
const ( const (
ConstLastIndex = 0xFFFF_FFFF ConstLastIndex = 0xFFFF_FFFF
) )
+11 -11
View File
@@ -5,16 +5,16 @@
package expr package expr
const ( const (
TypeAny = "any" TypeAny = "any"
TypeBoolean = "boolean" TypeBoolean = "boolean"
TypeFloat = "float" TypeFloat = "float"
TypeFraction = "fraction" TypeFraction = "fraction"
TypeHandle = "handle" TypeFileHandle = "file-handle"
TypeInt = "integer" TypeInt = "integer"
TypeItem = "item" TypeItem = "item"
TypeNumber = "number" TypeNumber = "number"
TypePair = "pair" TypePair = "pair"
TypeString = "string" TypeString = "string"
TypeListOf = "list-of-" TypeListOf = "list-of-"
TypeListOfStrings = "list-of-strings" TypeListOfStrings = "list-of-strings"
) )
+111 -63
View File
@@ -5,30 +5,45 @@
package expr package expr
import ( import (
"errors"
"io" "io"
) )
type dataCursor struct { type dataCursor struct {
ds map[string]Functor ds map[string]Functor
ctx ExprContext ctx ExprContext
index int initState bool // true if no item has prodiced yet (this replace di initial Next() call in the contructor)
resource any index int
nextFunc Functor count int
cleanFunc Functor current any
resetFunc Functor lastErr error
currentFunc Functor resource any
nextFunc Functor
cleanFunc Functor
resetFunc Functor
} }
func newDataCursor(ctx ExprContext, ds map[string]Functor) (dc *dataCursor) { func NewDataCursor(ctx ExprContext, ds map[string]Functor, resource any) (dc *dataCursor) {
dc = &dataCursor{ dc = &dataCursor{
ds: ds, ds: ds,
index: -1, initState: true,
ctx: ctx.Clone(), index: -1,
count: 0,
current: nil,
lastErr: nil,
resource: resource,
ctx: ctx.Clone(),
nextFunc: ds[NextName],
cleanFunc: ds[CleanName],
resetFunc: ds[ResetName],
} }
//dc.Next()
return return
} }
func (dc *dataCursor) Context() ExprContext {
return dc.ctx
}
func (dc *dataCursor) TypeName() string { func (dc *dataCursor) TypeName() string {
return "DataCursor" return "DataCursor"
} }
@@ -62,7 +77,7 @@ func (dc *dataCursor) String() string {
} }
func (dc *dataCursor) HasOperation(name string) (exists bool) { func (dc *dataCursor) HasOperation(name string) (exists bool) {
exists = name == indexName exists = name == IndexName
if !exists { if !exists {
f, ok := dc.ds[name] f, ok := dc.ds[name]
exists = ok && isFunctor(f) exists = ok && isFunctor(f)
@@ -70,8 +85,8 @@ func (dc *dataCursor) HasOperation(name string) (exists bool) {
return return
} }
func (dc *dataCursor) CallOperation(name string, args []any) (value any, err error) { func (dc *dataCursor) CallOperation(name string, args map[string]any) (value any, err error) {
if name == indexName { if name == IndexName {
value = int64(dc.Index()) value = int64(dc.Index())
} else if functor, ok := dc.ds[name]; ok && isFunctor(functor) { } else if functor, ok := dc.ds[name]; ok && isFunctor(functor) {
if functor == dc.cleanFunc { if functor == dc.cleanFunc {
@@ -80,7 +95,7 @@ func (dc *dataCursor) CallOperation(name string, args []any) (value any, err err
value, err = dc.Reset() value, err = dc.Reset()
} else { } else {
ctx := cloneContext(dc.ctx) ctx := cloneContext(dc.ctx)
value, err = functor.Invoke(ctx, name, []any{}) value, err = functor.InvokeNamed(ctx, name, args)
exportObjects(dc.ctx, ctx) exportObjects(dc.ctx, ctx)
} }
} else { } else {
@@ -93,15 +108,20 @@ func (dc *dataCursor) Reset() (success bool, err error) {
if dc.resetFunc != nil { if dc.resetFunc != nil {
if dc.resource != nil { if dc.resource != nil {
ctx := cloneContext(dc.ctx) ctx := cloneContext(dc.ctx)
if _, err = dc.resetFunc.Invoke(ctx, resetName, []any{dc.resource}); err == nil { actualParams := bindActualParams(dc.resetFunc, []any{dc.resource})
dc.index = -1 _, err = dc.resetFunc.InvokeNamed(ctx, ResetName, actualParams)
}
exportObjects(dc.ctx, ctx) exportObjects(dc.ctx, ctx)
dc.index = -1
dc.count = 0
dc.initState = true
dc.current = nil
dc.lastErr = nil
//dc.Next()
} else { } else {
err = errInvalidDataSource() err = errInvalidDataSource()
} }
} else { } else {
err = errNoOperation(resetName) err = errNoOperation(ResetName)
} }
success = err == nil success = err == nil
return return
@@ -111,82 +131,110 @@ func (dc *dataCursor) Clean() (success bool, err error) {
if dc.cleanFunc != nil { if dc.cleanFunc != nil {
if dc.resource != nil { if dc.resource != nil {
ctx := cloneContext(dc.ctx) ctx := cloneContext(dc.ctx)
_, err = dc.cleanFunc.Invoke(ctx, cleanName, []any{dc.resource}) actualParams := bindActualParams(dc.cleanFunc, []any{dc.resource})
dc.resource = nil _, err = dc.cleanFunc.InvokeNamed(ctx, CleanName, actualParams)
// dc.resource = nil
exportObjects(dc.ctx, ctx) exportObjects(dc.ctx, ctx)
} else { } else {
err = errInvalidDataSource() err = errInvalidDataSource()
} }
} else { } else {
err = errors.New("no 'clean' function defined in the data-source") err = errNoOperation(CleanName)
} }
success = err == nil success = err == nil
return return
} }
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
ctx := cloneContext(dc.ctx) dc.init()
if item, err = dc.currentFunc.Invoke(ctx, currentName, []any{}); err == nil && item == nil {
if dc.current != nil {
item = dc.current
} else {
err = io.EOF err = io.EOF
} }
exportObjects(dc.ctx, ctx)
return return
} }
func (dc *dataCursor) _Next() (item any, err error) { // must return io.EOF after the last item func (dc *dataCursor) checkFilter(filter Functor, item any) (accepted bool, err error) {
if dc.resource != nil { var v any
ctx := cloneContext(dc.ctx) var ok bool
// fmt.Printf("Entering Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0)) ctx := cloneContext(dc.ctx)
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil {
if item == nil { actualParams := bindActualParams(filter, []any{item, dc.index})
err = io.EOF if v, err = filter.InvokeNamed(ctx, FilterName, actualParams); err == nil && v != nil {
} else { if accepted, ok = v.(bool); !ok {
dc.index++ accepted = true // NOTE: A non-boolean value that is not nil means the item has been accepted
}
} }
// fmt.Printf("Exiting Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
exportObjects(dc.ctx, ctx)
// fmt.Printf("Outer-Ctx [%p]: %s\n", dc.ctx, CtxToString(dc.ctx, 0))
} else {
err = errInvalidDataSource()
} }
return return
} }
const filterName = "filter" func (dc *dataCursor) mapItem(mapper Functor, item any) (mappedItem any, err error) {
func (dc *dataCursor) filter(item any) (filterdItem any, err error) { ctx := cloneContext(dc.ctx)
if filter, ok := dc.ds[filterName]; ok { actualParams := bindActualParams(mapper, []any{item, dc.index})
ctx := cloneContext(dc.ctx) mappedItem, err = mapper.InvokeNamed(ctx, MapName, actualParams)
filterdItem, err = filter.Invoke(ctx, filterName, []any{item, dc.index});
} else {
filterdItem = item
}
return return
} }
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item func (dc *dataCursor) init() {
if dc.initState {
dc.initState = false
dc.Next()
}
}
func (dc *dataCursor) Next() (current any, err error) { // must return io.EOF after the last item
if dc.initState {
dc.init()
} else if err = dc.lastErr; err != nil {
return
}
current = dc.current
if dc.resource != nil { if dc.resource != nil {
ctx := cloneContext(dc.ctx) filter := dc.ds[FilterName]
// fmt.Printf("Entering Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0)) mapper := dc.ds[MapName]
for item == nil && err == nil { var item any
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil { for item == nil && dc.lastErr == nil {
ctx := cloneContext(dc.ctx)
dc.index++
actualParams := bindActualParams(dc.nextFunc, []any{dc.resource, dc.index})
if item, dc.lastErr = dc.nextFunc.InvokeNamed(ctx, NextName, actualParams); dc.lastErr == nil {
if item == nil { if item == nil {
err = io.EOF dc.lastErr = io.EOF
} else { } else {
dc.index++ accepted := true
item, err = dc.filter(item) if filter != nil {
if accepted, dc.lastErr = dc.checkFilter(filter, item); dc.lastErr != nil || !accepted {
item = nil
}
}
if accepted {
dc.count++
}
if item != nil && mapper != nil {
item, dc.lastErr = dc.mapItem(mapper, item)
}
} }
} }
exportObjects(dc.ctx, ctx)
}
dc.current = item
if dc.lastErr != nil {
dc.index--
dc.Clean()
} }
// fmt.Printf("Exiting Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
exportObjects(dc.ctx, ctx)
// fmt.Printf("Outer-Ctx [%p]: %s\n", dc.ctx, CtxToString(dc.ctx, 0))
} else { } else {
err = errInvalidDataSource() dc.lastErr = errInvalidDataSource()
} }
return return
} }
func (dc *dataCursor) Index() int { func (dc *dataCursor) Index() int {
return dc.index return dc.index - 1
}
func (dc *dataCursor) Count() int {
return dc.count
} }
+15
View File
@@ -18,7 +18,22 @@ func MakeDict() (dict *DictType) {
return return
} }
func NewDict(dictAny map[any]any) (dict *DictType) {
var d DictType
if dictAny != nil {
d = make(DictType, len(dictAny))
for i, item := range dictAny {
d[i] = item
}
} else {
d = make(DictType)
}
dict = &d
return
}
func newDict(dictAny map[any]*term) (dict *DictType) { func newDict(dictAny map[any]*term) (dict *DictType) {
// TODO Change wi a call to NewDict()
var d DictType var d DictType
if dictAny != nil { if dictAny != nil {
d = make(DictType, len(dictAny)) d = make(DictType, len(dictAny))
+9 -3
View File
@@ -7,17 +7,23 @@ package expr
// ----Expression Context // ----Expression Context
type ExprContext interface { type ExprContext interface {
Clone() ExprContext Clone() ExprContext
// Merge(ctx ExprContext)
SetParent(ctx ExprContext) SetParent(ctx ExprContext)
GetParent() (ctx ExprContext) GetParent() (ctx ExprContext)
GetVar(varName string) (value any, exists bool) GetVar(varName string) (value any, exists bool)
GetLast() any GetLast() any
SetVar(varName string, value any) SetVar(varName string, value any)
UnsafeSetVar(varName string, value any) UnsafeSetVar(varName string, value any)
EnumVars(func(name string) (accept bool)) (varNames []string) EnumVars(func(name string) (accept bool)) (varNames []string)
VarCount() int
DeleteVar(varName string)
EnumFuncs(func(name string) (accept bool)) (funcNames []string) EnumFuncs(func(name string) (accept bool)) (funcNames []string)
FuncCount() int
DeleteFunc(funcName 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 map[string]any) (result any, err error)
RegisterFuncInfo(info ExprFunc) RegisterFuncInfo(info ExprFunc)
RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) error RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) (funcInfo ExprFunc, err error)
} }
+4 -2
View File
@@ -6,7 +6,8 @@ package expr
// ---- Functor interface // ---- Functor interface
type Functor interface { type Functor interface {
Invoke(ctx ExprContext, name string, args []any) (result any, err error) Typer
InvokeNamed(ctx ExprContext, name string, args map[string]any) (result any, err error)
SetFunc(info ExprFunc) SetFunc(info ExprFunc)
GetFunc() ExprFunc GetFunc() ExprFunc
GetParams() []ExprFuncParam GetParams() []ExprFuncParam
@@ -31,7 +32,8 @@ type ExprFunc interface {
MaxArgs() int MaxArgs() int
Functor() Functor Functor() Functor
Params() []ExprFuncParam Params() []ExprFuncParam
ParamSpec(paramName string) ExprFuncParam
ReturnType() string ReturnType() string
PrepareCall(parentCtx ExprContext, name string, varParams *[]any) (ctx ExprContext, err error) PrepareCall(name string, actualParams map[string]any) (err error)
AllocContext(parentCtx ExprContext) (ctx ExprContext) AllocContext(parentCtx ExprContext) (ctx ExprContext)
} }
+1
View File
@@ -6,6 +6,7 @@ package expr
// ----Expression interface // ----Expression interface
type Expr interface { type Expr interface {
Typer
Eval(ctx ExprContext) (result any, err error) Eval(ctx ExprContext) (result any, err error)
String() string String() string
} }
+153 -23
View File
@@ -6,11 +6,12 @@ package expr
import ( import (
"fmt" "fmt"
"strconv"
"strings" "strings"
) )
// ---- Function template // ---- Function template
type FuncTemplate func(ctx ExprContext, name string, args []any) (result any, err error) type FuncTemplate func(ctx ExprContext, name string, args map[string]any) (result any, err error)
// ---- Common functor definition // ---- Common functor definition
type baseFunctor struct { type baseFunctor struct {
@@ -137,10 +138,6 @@ func newFuncInfo(name string, functor Functor, returnType string, params []ExprF
return info, nil 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 { func (info *funcInfo) Params() []ExprFuncParam {
return info.formalParams return info.formalParams
} }
@@ -216,37 +213,133 @@ func (info *funcInfo) AllocContext(parentCtx ExprContext) (ctx ExprContext) {
return return
} }
func (info *funcInfo) PrepareCall(parentCtx ExprContext, name string, varActualParams *[]any) (ctx ExprContext, err error) { func (info *funcInfo) ParamSpec(paramName string) ExprFuncParam {
passedCount := len(*varActualParams) for _, spec := range info.formalParams {
if info.MinArgs() > passedCount { if spec.Name() == paramName {
err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount) return spec
}
}
return nil
}
func initActualParams(ctx ExprContext, info ExprFunc, callTerm *term) (actualParams map[string]any, err error) {
var varArgs []any
var varName string
namedParamsStarted := false
formalParams := info.Params()
actualParams = make(map[string]any, len(formalParams))
if callTerm == nil {
return
} }
for i := passedCount; i < len(info.formalParams); i++ { for i, tree := range callTerm.children {
p := info.formalParams[i] var paramValue any
if !p.IsDefault() { paramCtx := ctx.Clone()
if paramValue, err = tree.compute(paramCtx); err != nil {
break
}
if paramName, namedParam := getAssignVarName(tree); namedParam {
if info.ParamSpec(paramName) == nil {
err = fmt.Errorf("%s(): unknown param %q", info.Name(), paramName)
break
}
actualParams[paramName] = paramValue
namedParamsStarted = true
} else if !namedParamsStarted {
if varArgs != nil {
varArgs = append(varArgs, paramValue)
} else if i < len(formalParams) {
spec := formalParams[i]
if spec.IsRepeat() {
varArgs = make([]any, 0, len(callTerm.children)-i)
varArgs = append(varArgs, paramValue)
varName = spec.Name()
} else {
actualParams[spec.Name()] = paramValue
}
} else {
err = ErrTooManyParams(info.Name(), len(formalParams), len(callTerm.children))
break
}
} else {
err = fmt.Errorf("%s(): positional param nr %d not allowed after named params", info.Name(), i+1)
break break
} }
*varActualParams = append(*varActualParams, p.DefaultValue())
} }
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(*varActualParams) { if err == nil {
err = ErrTooMuchParams(name, info.MaxArgs(), len(*varActualParams)) if varArgs != nil {
actualParams[varName] = varArgs
}
}
return
}
func (info *funcInfo) PrepareCall(name string, actualParams map[string]any) (err error) {
passedCount := len(actualParams)
if info.MinArgs() > passedCount {
err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
return
} }
if err == nil { if passedCount < len(info.formalParams) {
ctx = info.AllocContext(parentCtx) for _, p := range info.formalParams {
if _, exists := actualParams[p.Name()]; !exists {
if !p.IsDefault() {
break
}
if p.IsRepeat() {
varArgs := make([]any, 1)
varArgs[0] = p.DefaultValue()
actualParams[p.Name()] = varArgs
} else {
actualParams[p.Name()] = p.DefaultValue()
}
}
}
}
if info.MaxArgs() >= 0 && info.MaxArgs() < len(actualParams) {
err = ErrTooManyParams(name, info.MaxArgs(), len(actualParams))
} }
return return
} }
// ----- Call a function --- // ----- Call a function ---
func CallFunction(parentCtx ExprContext, name string, actualParams []any) (result any, err error) { func getAssignVarName(t *term) (name string, ok bool) {
if info, exists, _ := GetFuncInfo(parentCtx, name); exists { if ok = t.symbol() == SymEqual; ok {
var ctx ExprContext name = t.children[0].source()
if ctx, err = info.PrepareCall(parentCtx, name, &actualParams); err == nil { }
functor := info.Functor() return
result, err = functor.Invoke(ctx, name, actualParams) }
func CallFunctionByTerm(parentCtx ExprContext, name string, callTerm *term) (result any, err error) {
var actualParams map[string]any
if info, exists := GetFuncInfo(parentCtx, name); exists {
if actualParams, err = initActualParams(parentCtx, info, callTerm); err == nil {
ctx := info.AllocContext(parentCtx)
if err = info.PrepareCall(name, actualParams); err == nil {
functor := info.Functor()
result, err = functor.InvokeNamed(ctx, name, actualParams)
exportObjectsToParent(ctx)
}
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
func CallFunctionByArgs(parentCtx ExprContext, name string, args []any) (result any, err error) {
var actualParams map[string]any
if info, exists := GetFuncInfo(parentCtx, name); exists {
functor := info.Functor()
actualParams = bindActualParams(functor, args)
ctx := info.AllocContext(parentCtx)
if err = info.PrepareCall(name, actualParams); err == nil {
result, err = functor.InvokeNamed(ctx, name, actualParams)
exportObjectsToParent(ctx) exportObjectsToParent(ctx)
} }
} else { } else {
@@ -254,3 +347,40 @@ func CallFunction(parentCtx ExprContext, name string, actualParams []any) (resul
} }
return return
} }
func CallFunctionByParams(parentCtx ExprContext, name string, params map[string]any) (result any, err error) {
var actualParams map[string]any
if info, exists := GetFuncInfo(parentCtx, name); exists {
functor := info.Functor()
ctx := info.AllocContext(parentCtx)
if err = info.PrepareCall(name, actualParams); err == nil {
result, err = functor.InvokeNamed(ctx, name, actualParams)
exportObjectsToParent(ctx)
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
func GetParam(args map[string]any, paramName string, paramNum int) (value any, exists bool) {
if value, exists = args[paramName]; !exists {
if paramNum > 0 && paramNum <= len(args) {
value, exists = args["arg"+strconv.Itoa(paramNum)]
}
}
return
}
func bindActualParams(functor Functor, args []any) (actualParams map[string]any) {
formalParams := functor.GetParams()
actualParams = make(map[string]any, len(args))
for i, arg := range args {
if i < len(formalParams) {
actualParams[formalParams[i].Name()] = arg
} else {
actualParams["arg"+strconv.Itoa(i+1)] = arg
}
}
return
}
+14 -1
View File
@@ -55,7 +55,20 @@ func GetLocalFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool)
} }
return return
} }
func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool, ownerCtx ExprContext) {
func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool) {
// if len(name) > 0 {
// if item, exists = GetLocalFuncInfo(ctx, name); exists {
// ownerCtx = ctx
// } else if item, exists = globalCtx.GetFuncInfo(name); exists {
// ownerCtx = globalCtx
// }
// }
item, exists, _ = GetFuncInfoAndOwner(ctx, name)
return
}
func GetFuncInfoAndOwner(ctx ExprContext, name string) (item ExprFunc, exists bool, ownerCtx ExprContext) {
if len(name) > 0 { if len(name) > 0 {
if item, exists = GetLocalFuncInfo(ctx, name); exists { if item, exists = GetLocalFuncInfo(ctx, name); exists {
ownerCtx = ctx ownerCtx = ctx
+8
View File
@@ -75,7 +75,11 @@ func isFile(filePath string) bool {
} }
func searchAmongPath(filename string, dirList []string) (filePath string) { func searchAmongPath(filename string, dirList []string) (filePath string) {
var err error
for _, dir := range dirList { for _, dir := range dirList {
if dir, err = ExpandPath(dir); err != nil {
continue
}
if fullPath := path.Join(dir, filename); isFile(fullPath) { if fullPath := path.Join(dir, filename); isFile(fullPath) {
filePath = fullPath filePath = fullPath
break break
@@ -90,6 +94,10 @@ func isPathRelative(filePath string) bool {
} }
func makeFilepath(filename string, dirList []string) (filePath string, err error) { func makeFilepath(filename string, dirList []string) (filePath string, err error) {
if filename, err = ExpandPath(filename); err != nil {
return
}
if path.IsAbs(filename) || isPathRelative(filename) { if path.IsAbs(filename) || isPathRelative(filename) {
if isFile(filename) { if isFile(filename) {
filePath = filename filePath = filename
+11 -8
View File
@@ -12,13 +12,15 @@ import (
// Operator names // Operator names
const ( const (
initName = "init" InitName = "init"
cleanName = "clean" CleanName = "clean"
resetName = "reset" ResetName = "reset"
nextName = "next" NextName = "next"
currentName = "current" CurrentName = "current"
indexName = "index" IndexName = "index"
countName = "count" CountName = "count"
FilterName = "filter"
MapName = "map"
) )
type Iterator interface { type Iterator interface {
@@ -26,12 +28,13 @@ type Iterator interface {
Next() (item any, err error) // must return io.EOF after the last item Next() (item any, err error) // must return io.EOF after the last item
Current() (item any, err error) Current() (item any, err error)
Index() int Index() int
Count() int
} }
type ExtIterator interface { type ExtIterator interface {
Iterator Iterator
HasOperation(name string) bool HasOperation(name string) bool
CallOperation(name string, args []any) (value any, err error) CallOperation(name string, args map[string]any) (value any, err error)
} }
func errNoOperation(name string) error { func errNoOperation(name string) error {
+14 -5
View File
@@ -90,17 +90,21 @@ func (it *ListIterator) TypeName() string {
} }
func (it *ListIterator) HasOperation(name string) bool { func (it *ListIterator) HasOperation(name string) bool {
yes := name == resetName || name == indexName || name == countName yes := name == NextName || name == ResetName || name == IndexName || name == CountName || name == CurrentName
return yes return yes
} }
func (it *ListIterator) CallOperation(name string, args []any) (v any, err error) { func (it *ListIterator) CallOperation(name string, args map[string]any) (v any, err error) {
switch name { switch name {
case resetName: case NextName:
v, err = it.Next()
case ResetName:
v, err = it.Reset() v, err = it.Reset()
case indexName: case IndexName:
v = int64(it.Index()) v = int64(it.Index())
case countName: case CurrentName:
v, err = it.Current()
case CountName:
v = it.count v = it.count
default: default:
err = errNoOperation(name) err = errNoOperation(name)
@@ -139,7 +143,12 @@ func (it *ListIterator) Index() int {
return it.index return it.index
} }
func (it *ListIterator) Count() int {
return it.count
}
func (it *ListIterator) Reset() (bool, error) { func (it *ListIterator) Reset() (bool, error) {
it.index = it.start - it.step it.index = it.start - it.step
it.count = 0
return true, nil return true, nil
} }
+18 -12
View File
@@ -21,20 +21,26 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
} }
// -------- eval func call // -------- eval func call
// func _evalFuncCall(ctx ExprContext, opTerm *term) (v any, err error) {
// name, _ := opTerm.tk.Value.(string)
// params := make([]any, len(opTerm.children), len(opTerm.children)+5)
// for i, tree := range opTerm.children {
// var param any
// if param, err = tree.compute(ctx); err != nil {
// break
// }
// params[i] = param
// }
// if err == nil {
// v, err = CallFunction(ctx, name, params)
// }
// return
// }
func evalFuncCall(ctx ExprContext, opTerm *term) (v any, err error) { func evalFuncCall(ctx ExprContext, opTerm *term) (v any, err error) {
name, _ := opTerm.tk.Value.(string) name, _ := opTerm.tk.Value.(string)
params := make([]any, len(opTerm.children), len(opTerm.children)+5) v, err = CallFunctionByTerm(ctx, name, opTerm)
for i, tree := range opTerm.children {
var param any
if param, err = tree.compute(ctx); err != nil {
break
}
params[i] = param
}
if err == nil {
v, err = CallFunction(ctx, name, params)
}
return return
} }
+12 -29
View File
@@ -11,22 +11,6 @@ import (
// -------- iterator term // -------- iterator term
// func newDsIteratorTerm(tk *Token, dsTerm *term, args []*term) *term {
// tk.Sym = SymIterator
// children := make([]*term, 0, 1+len(args))
// children = append(children, dsTerm)
// children = append(children, args...)
// return &term{
// tk: *tk,
// parent: nil,
// children: children,
// position: posLeaf,
// priority: priValue,
// evalFunc: evalIterator,
// }
// }
func newIteratorTerm(tk *Token, args []*term) *term { func newIteratorTerm(tk *Token, args []*term) *term {
tk.Sym = SymIterator tk.Sym = SymIterator
return &term{ return &term{
@@ -66,8 +50,8 @@ func evalFirstChild(ctx ExprContext, iteratorTerm *term) (value any, err error)
func getDataSourceDict(iteratorTerm *term, firstChildValue any) (ds map[string]Functor, err error) { func getDataSourceDict(iteratorTerm *term, firstChildValue any) (ds map[string]Functor, err error) {
if dictAny, ok := firstChildValue.(*DictType); ok { if dictAny, ok := firstChildValue.(*DictType); ok {
requiredFields := []string{currentName, nextName} requiredFields := []string{NextName}
fieldsMask := 0b11 fieldsMask := 0b1
foundFields := 0 foundFields := 0
ds = make(map[string]Functor) ds = make(map[string]Functor)
for keyAny, item := range *dictAny { for keyAny, item := range *dictAny {
@@ -88,7 +72,6 @@ func getDataSourceDict(iteratorTerm *term, firstChildValue any) (ds map[string]F
missingFields = append(missingFields, field) missingFields = append(missingFields, field)
} }
} }
// err = fmt.Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", "))
err = iteratorTerm.children[0].Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", ")) err = iteratorTerm.children[0].Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", "))
} }
} }
@@ -108,9 +91,10 @@ func evalIterator(ctx ExprContext, opTerm *term) (v any, err error) {
} }
if ds != nil { if ds != nil {
dc := newDataCursor(ctx, ds) var dc *dataCursor
if initFunc, exists := ds[initName]; exists && initFunc != nil { if initFunc, exists := ds[InitName]; exists && initFunc != nil {
var args []any var args []any
var resource any
if len(opTerm.children) > 1 { if len(opTerm.children) > 1 {
if args, err = evalTermArray(ctx, opTerm.children[1:]); err != nil { if args, err = evalTermArray(ctx, opTerm.children[1:]); err != nil {
return return
@@ -119,18 +103,17 @@ func evalIterator(ctx ExprContext, opTerm *term) (v any, err error) {
args = []any{} args = []any{}
} }
initCtx := dc.ctx.Clone() actualParams := bindActualParams(initFunc, args)
if dc.resource, err = initFunc.Invoke(initCtx, initName, args); err != nil {
initCtx := ctx.Clone()
if resource, err = initFunc.InvokeNamed(initCtx, InitName, actualParams); err != nil {
return return
} }
exportObjects(dc.ctx, initCtx) dcCtx := ctx.Clone()
exportObjects(dcCtx, initCtx)
dc = NewDataCursor(dcCtx, ds, resource)
} }
dc.nextFunc = ds[nextName]
dc.currentFunc = ds[currentName]
dc.cleanFunc = ds[cleanName]
dc.resetFunc = ds[resetName]
v = dc v = dc
} else if list, ok := firstChildValue.(*ListType); ok { } else if list, ok := firstChildValue.(*ListType); ok {
var args []any var args []any
+1 -1
View File
@@ -25,7 +25,7 @@ func evalVar(ctx ExprContext, opTerm *term) (v any, err error) {
var exists bool var exists bool
name := opTerm.source() name := opTerm.source()
if v, exists = GetVar(ctx, name); !exists { if v, exists = GetVar(ctx, name); !exists {
if info, exists, _ := GetFuncInfo(ctx, name); exists { if info, exists := GetFuncInfo(ctx, name); exists {
v = info.Functor() v = info.Functor()
} else { } else {
err = fmt.Errorf("undefined variable or function %q", name) err = fmt.Errorf("undefined variable or function %q", name)
+3 -3
View File
@@ -11,7 +11,7 @@ func newDefaultTerm(tk *Token) (inst *term) {
tk: *tk, tk: *tk,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priCoalesce, priority: priDefault,
evalFunc: evalDefault, evalFunc: evalDefault,
} }
} }
@@ -45,7 +45,7 @@ func newAlternateTerm(tk *Token) (inst *term) {
tk: *tk, tk: *tk,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priCoalesce, priority: priDefault,
evalFunc: evalAlternate, evalFunc: evalAlternate,
} }
} }
@@ -81,7 +81,7 @@ func newDefaultAssignTerm(tk *Token) (inst *term) {
tk: *tk, tk: *tk,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priCoalesce, priority: priDefault,
evalFunc: evalAssignDefault, evalFunc: evalAssignDefault,
} }
} }
+1 -1
View File
@@ -32,7 +32,7 @@ func evalDot(ctx ExprContext, opTerm *term) (v any, err error) {
if indexTerm.symbol() == SymVariable /*|| indexTerm.symbol() == SymString */ { if indexTerm.symbol() == SymVariable /*|| indexTerm.symbol() == SymString */ {
opName := indexTerm.source() opName := indexTerm.source()
if unboxedValue.HasOperation(opName) { if unboxedValue.HasOperation(opName) {
v, err = unboxedValue.CallOperation(opName, []any{}) v, err = unboxedValue.CallOperation(opName, map[string]any{})
} else { } else {
err = indexTerm.Errorf("this iterator do not support the %q command", opName) err = indexTerm.Errorf("this iterator do not support the %q command", opName)
v = false v = false
+5 -4
View File
@@ -25,8 +25,8 @@ func evalInclude(ctx ExprContext, opTerm *term) (v any, err error) {
count := 0 count := 0
if IsList(childValue) { if IsList(childValue) {
list, _ := childValue.([]any) list, _ := childValue.(*ListType)
for i, filePathSpec := range list { for i, filePathSpec := range *list {
if filePath, ok := filePathSpec.(string); ok { if filePath, ok := filePathSpec.(string); ok {
if v, err = EvalFile(ctx, filePath); err == nil { if v, err = EvalFile(ctx, filePath); err == nil {
count++ count++
@@ -47,8 +47,9 @@ func evalInclude(ctx ExprContext, opTerm *term) (v any, err error) {
} else { } else {
err = opTerm.errIncompatibleType(childValue) err = opTerm.errIncompatibleType(childValue)
} }
if err == nil { if err != nil {
v = count //v = count
v = nil
} }
return return
} }
+2 -1
View File
@@ -33,5 +33,6 @@ func evalIterValue(ctx ExprContext, opTerm *term) (v any, err error) {
// init // init
func init() { func init() {
registerTermConstructor(SymOpenClosedRound, newIterValueTerm) // registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
registerTermConstructor(SymCaret, newIterValueTerm)
} }
+7 -7
View File
@@ -30,16 +30,16 @@ func evalLength(ctx ExprContext, opTerm *term) (v any, err error) {
s, _ := childValue.(string) s, _ := childValue.(string)
v = int64(len(s)) v = int64(len(s))
} else if IsDict(childValue) { } else if IsDict(childValue) {
// m, _ := childValue.(map[any]any)
m, _ := childValue.(*DictType) m, _ := childValue.(*DictType)
v = int64(len(*m)) v = int64(len(*m))
} else if it, ok := childValue.(Iterator); ok { } else if it, ok := childValue.(Iterator); ok {
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) { v = int64(it.Count())
count, _ := extIt.CallOperation(countName, nil) // if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(CountName) {
v, _ = ToGoInt(count, "") // count, _ := extIt.CallOperation(CountName, nil)
} else { // v, _ = ToGoInt(count, "")
v = int64(it.Index() + 1) // } else {
} // v = int64(it.Index() + 1)
// }
} else { } else {
err = opTerm.errIncompatibleType(childValue) err = opTerm.errIncompatibleType(childValue)
} }
+2 -24
View File
@@ -4,10 +4,6 @@
// operator-plugin.go // operator-plugin.go
package expr package expr
import (
"io"
)
//-------- plugin term //-------- plugin term
func newPluginTerm(tk *Token) (inst *term) { func newPluginTerm(tk *Token) (inst *term) {
@@ -22,31 +18,13 @@ func newPluginTerm(tk *Token) (inst *term) {
func evalPlugin(ctx ExprContext, opTerm *term) (v any, err error) { func evalPlugin(ctx ExprContext, opTerm *term) (v any, err error) {
var childValue any var childValue any
var moduleSpec any var count int
if childValue, err = opTerm.evalPrefix(ctx); err != nil { if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return return
} }
dirList := buildSearchDirList("plugin", ENV_EXPR_PLUGIN_PATH) if count, err = importPluginFromSearchPath(childValue); err == nil {
count := 0
it := NewAnyIterator(childValue)
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
if module, ok := moduleSpec.(string); ok {
if err = importPlugin(dirList, module); err != nil {
break
}
count++
} else {
err = opTerm.Errorf("expected string as item nr %d, got %s", it.Index()+1, TypeName(moduleSpec))
break
}
}
if err == io.EOF {
err = nil
}
if err == nil {
v = int64(count) v = int64(count)
} }
return return
+32
View File
@@ -35,7 +35,39 @@ func evalPostInc(ctx ExprContext, opTerm *term) (v any, err error) {
return return
} }
// -------- post decrement term
func newPostDecTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: make([]*term, 0, 1),
position: posPostfix,
priority: priIncDec,
evalFunc: evalPostDec,
}
}
func evalPostDec(ctx ExprContext, opTerm *term) (v any, err error) {
var childValue any
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
/* if it, ok := childValue.(Iterator); ok {
v, err = it.Next()
} else */if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
v = childValue
i, _ := childValue.(int64)
ctx.SetVar(opTerm.children[0].source(), i-1)
} else {
err = opTerm.errIncompatibleType(childValue)
}
return
}
// init // init
func init() { func init() {
registerTermConstructor(SymDoublePlus, newPostIncTerm) registerTermConstructor(SymDoublePlus, newPostIncTerm)
registerTermConstructor(SymDoubleMinus, newPostDecTerm)
} }
+69
View File
@@ -0,0 +1,69 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-unset.go
package expr
import "strings"
//-------- unset term
func newUnsetTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priSign,
evalFunc: evalUnset,
}
}
func deleteContextItem(ctx ExprContext, opTerm *term, item any) (deleted bool, err error) {
if name, ok := item.(string); ok {
var size int
if strings.HasSuffix(name, "()") {
size = ctx.FuncCount()
ctx.DeleteFunc(strings.TrimRight(name, "()"))
deleted = ctx.FuncCount() < size
} else {
size = ctx.VarCount()
ctx.DeleteVar(name)
deleted = ctx.VarCount() < size
}
} else {
err = opTerm.errIncompatibleType(item)
}
return
}
func evalUnset(ctx ExprContext, opTerm *term) (v any, err error) {
var childValue any
var deleted bool
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
count := 0
if IsList(childValue) {
list, _ := childValue.(*ListType)
for _, item := range *list {
if deleted, err = deleteContextItem(ctx, opTerm, item); err != nil {
break
} else if deleted {
count++
}
}
} else if deleted, err = deleteContextItem(ctx, opTerm, childValue); err == nil && deleted {
count++
}
if err == nil {
v = int64(count)
}
return
}
// init
func init() {
registerTermConstructor(SymKwUnset, newUnsetTerm)
}
+74 -18
View File
@@ -18,6 +18,12 @@ func NewParser() (p *parser) {
return p return p
} }
func (parser *parser) Next(scanner *scanner) (tk *Token) {
for tk = scanner.Next(); tk.IsSymbol(SymComment); tk = scanner.Next() {
}
return
}
func (parser *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token) (tree *term, err error) { func (parser *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token) (tree *term, err error) {
args := make([]*term, 0, 10) args := make([]*term, 0, 10)
itemExpected := false itemExpected := false
@@ -57,11 +63,11 @@ func (parser *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
itemExpected := false itemExpected := false
tk := scanner.Previous() tk := scanner.Previous()
for lastSym != SymClosedRound && lastSym != SymEos { for lastSym != SymClosedRound && lastSym != SymEos {
tk = scanner.Next() tk = parser.Next(scanner)
if tk.IsSymbol(SymIdentifier) { if tk.IsSymbol(SymIdentifier) {
param := newTerm(tk) param := newTerm(tk)
args = append(args, param) args = append(args, param)
tk = scanner.Next() tk = parser.Next(scanner)
if tk.Sym == SymEqual { if tk.Sym == SymEqual {
var paramExpr *ast var paramExpr *ast
defaultParamsStarted = true defaultParamsStarted = true
@@ -86,7 +92,7 @@ func (parser *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
err = tk.ErrorExpectedGot(")") err = tk.ErrorExpectedGot(")")
} }
if err == nil { if err == nil {
tk = scanner.Next() tk = parser.Next(scanner)
if tk.IsSymbol(SymOpenBrace) { if tk.IsSymbol(SymOpenBrace) {
body, err = parser.parseGeneral(scanner, true, true, SymClosedBrace) body, err = parser.parseGeneral(scanner, true, true, SymClosedBrace)
} else { } else {
@@ -184,8 +190,8 @@ func (parser *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree
return return
} }
func (parser *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, err error) { func (parser *parser) parseDictKey(scanner *scanner) (key any, err error) {
tk := scanner.Next() tk := parser.Next(scanner)
if tk.Sym == SymError { if tk.Sym == SymError {
err = tk.Error() err = tk.Error()
return return
@@ -194,7 +200,7 @@ func (parser *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any,
return return
} }
if tk.Sym == SymInteger || tk.Sym == SymString { if tk.Sym == SymInteger || tk.Sym == SymString {
tkSep := scanner.Next() tkSep := parser.Next(scanner)
if tkSep.Sym != SymColon { if tkSep.Sym != SymColon {
err = tkSep.ErrorExpectedGot(":") err = tkSep.ErrorExpectedGot(":")
} else { } else {
@@ -213,7 +219,7 @@ func (parser *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtr
for lastSym != SymClosedBrace && lastSym != SymEos { for lastSym != SymClosedBrace && lastSym != SymEos {
var subTree *ast var subTree *ast
var key any var key any
if key, err = parser.parseDictKey(scanner, allowVarRef); err != nil { if key, err = parser.parseDictKey(scanner); err != nil {
break break
} else if key == nil { } else if key == nil {
tk := scanner.Previous() tk := scanner.Previous()
@@ -251,7 +257,7 @@ func (parser *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtr
func (parser *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaultCase bool) (caseTerm *term, err error) { func (parser *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaultCase bool) (caseTerm *term, err error) {
var filterList *term var filterList *term
var caseExpr *ast var caseExpr *ast
tk := scanner.Next() tk := parser.Next(scanner)
startRow := tk.row startRow := tk.row
startCol := tk.col startCol := tk.col
if tk.Sym == SymOpenSquare { if tk.Sym == SymOpenSquare {
@@ -262,7 +268,7 @@ func (parser *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defa
if filterList, err = parser.parseList(scanner, false, allowVarRef); err != nil { if filterList, err = parser.parseList(scanner, false, allowVarRef); err != nil {
return return
} }
tk = scanner.Next() tk = parser.Next(scanner)
startRow = tk.row startRow = tk.row
startCol = tk.col startCol = tk.col
} else if !defaultCase { } else if !defaultCase {
@@ -299,7 +305,7 @@ func addSelectorCase(selectorTerm, caseTerm *term) {
func (parser *parser) parseSelector(scanner *scanner, tree *ast, allowVarRef bool) (selectorTerm *term, err error) { func (parser *parser) parseSelector(scanner *scanner, tree *ast, allowVarRef bool) (selectorTerm *term, err error) {
var caseTerm *term var caseTerm *term
tk := scanner.makeToken(SymSelector, '?') tk := scanner.makeToken(SymSelector, '?')
if selectorTerm, err = tree.addToken2(tk); err != nil { if selectorTerm, err = tree.addToken(tk); err != nil {
return return
} }
@@ -337,13 +343,14 @@ func (parser *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarR
var selectorTerm *term = nil var selectorTerm *term = nil
var currentTerm *term = nil var currentTerm *term = nil
var tk *Token var tk *Token
tree = NewAst() tree = NewAst()
firstToken := true firstToken := true
// lastSym := SymUnknown // lastSym := SymUnknown
for tk = scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); /*&& !areSymbolsOutOfCtx(tk, selectorTerm, SymColon, SymDoubleColon)*/ tk = scanner.Next() { for tk = parser.Next(scanner); err == nil && tk != nil && !tk.IsTerm(termSymbols); /*&& !areSymbolsOutOfCtx(tk, selectorTerm, SymColon, SymDoubleColon)*/ tk = parser.Next(scanner) {
if tk.Sym == SymComment { // if tk.Sym == SymComment {
continue // continue
} // }
if tk.Sym == SymSemiColon { if tk.Sym == SymSemiColon {
if allowForest { if allowForest {
@@ -409,7 +416,7 @@ func (parser *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarR
} }
case SymEqual: case SymEqual:
// if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil { // if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
currentTerm, err = tree.addToken2(tk) currentTerm, err = tree.addToken(tk)
// } // }
case SymFuncDef: case SymFuncDef:
var funcDefTerm *term var funcDefTerm *term
@@ -427,7 +434,7 @@ func (parser *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarR
if tk.source[0] == '@' && !allowVarRef { if tk.source[0] == '@' && !allowVarRef {
err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source) err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source)
} else { } else {
currentTerm, err = tree.addToken2(tk) currentTerm, err = tree.addToken(tk)
} }
case SymQuestion: case SymQuestion:
if selectorTerm, err = parser.parseSelector(scanner, tree, allowVarRef); err == nil { if selectorTerm, err = parser.parseSelector(scanner, tree, allowVarRef); err == nil {
@@ -444,14 +451,16 @@ func (parser *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarR
} }
} }
} else { } else {
currentTerm, err = tree.addToken2(tk) currentTerm, err = tree.addToken(tk)
} }
if tk.IsSymbol(SymColon) { if tk.IsSymbol(SymColon) {
// Colon outside a selector term acts like a separator // Colon outside a selector term acts like a separator
firstToken = true firstToken = true
} }
case SymPlusEqual, SymMinusEqual, SymStarEqual:
currentTerm, err = parser.expandOpAssign(scanner, tree, tk, allowVarRef)
default: default:
currentTerm, err = tree.addToken2(tk) currentTerm, err = tree.addToken(tk)
} }
if currentTerm != nil && currentTerm.tk.Sym != SymSelector && currentTerm.parent != nil && currentTerm.parent.tk.Sym != SymSelector { if currentTerm != nil && currentTerm.tk.Sym != SymSelector && currentTerm.parent != nil && currentTerm.parent.tk.Sym != SymSelector {
@@ -460,6 +469,7 @@ func (parser *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarR
} }
// lastSym = tk.Sym // lastSym = tk.Sym
} }
if err == nil { if err == nil {
err = tk.Error() err = tk.Error()
} }
@@ -472,3 +482,49 @@ func (parser *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarR
// } // }
// return // return
// } // }
func (parser *parser) expandOpAssign(scanner * scanner, tree *ast, tk *Token, allowVarRef bool) (t *term, err error) {
var opSym Symbol
var opString string
if tree.root != nil {
switch tk.Sym {
case SymPlusEqual:
opString = "+"
opSym = SymPlus
case SymMinusEqual:
opString = "-"
opSym = SymMinus
case SymStarEqual:
opString = "*"
opSym = SymStar
default:
err = tk.Errorf("unsopported operator %q", tk.source)
return
}
leftExpr := tree.root.Clone()
leftExpr.setParent(nil)
if t, err = tree.addToken(NewToken(tk.row, tk.col, SymEqual, "=")); err == nil {
t = leftExpr
if err = tree.addTerm(leftExpr); err == nil {
if t, err = tree.addToken(NewToken(tk.row, tk.col, opSym, opString)); err == nil {
var subTree *ast
if subTree, err = parser.parseGeneral(scanner, false, allowVarRef, SymSemiColon, SymClosedRound, SymClosedBrace, SymClosedSquare); err == nil {
if scanner.Previous().IsOneOfA(SymSemiColon, SymClosedRound, SymClosedBrace, SymClosedSquare) {
if err = scanner.UnreadToken(); err != nil {
return
}
}
subTree.root.priority = priValue
err = tree.addTerm(newExprTerm(subTree.root))
t = subTree.root
}
}
}
}
} else {
err = tk.Errorf("left operand of %q must be a variable or a variable expression", tk)
}
return
}
+24
View File
@@ -6,6 +6,7 @@ package expr
import ( import (
"fmt" "fmt"
"io"
"os" "os"
"plugin" "plugin"
"strings" "strings"
@@ -90,6 +91,29 @@ func importPlugin( /*ctx ExprContext,*/ dirList []string, name string) (err erro
return return
} }
func importPluginFromSearchPath(name any) (count int, err error) {
var moduleSpec any
dirList := buildSearchDirList("plugin", ENV_EXPR_PLUGIN_PATH)
count = 0
it := NewAnyIterator(name)
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
if module, ok := moduleSpec.(string); ok {
if err = importPlugin(dirList, module); err != nil {
break
}
count++
} else {
err = fmt.Errorf("expected string as item nr %d, got %s", it.Index()+1, TypeName(moduleSpec))
break
}
}
if err == io.EOF {
err = nil
}
return
}
func loadModules(dirList []string, moduleNames []string) (err error) { func loadModules(dirList []string, moduleNames []string) (err error) {
for _, name := range moduleNames { for _, name := range moduleNames {
if err1 := importPlugin(dirList, name); err1 != nil { if err1 := importPlugin(dirList, name); err1 != nil {
+25 -7
View File
@@ -16,6 +16,7 @@ import (
type scanner struct { type scanner struct {
current *Token current *Token
prev *Token prev *Token
stage *Token
stream *bufio.Reader stream *bufio.Reader
row int row int
column int column int
@@ -74,6 +75,16 @@ func (scanner *scanner) unreadChar() (err error) {
return return
} }
func (scanner *scanner) UnreadToken() (err error) {
if scanner.stage == nil {
scanner.stage = scanner.current
scanner.current = scanner.prev
} else {
err = fmt.Errorf("staging already present, currently one level only of staging is allowed")
}
return
}
func (scanner *scanner) lastPos() (r, c int) { func (scanner *scanner) lastPos() (r, c int) {
if scanner.prev != nil { if scanner.prev != nil {
r = scanner.prev.row r = scanner.prev.row
@@ -89,7 +100,12 @@ func (scanner *scanner) Previous() *Token {
func (scanner *scanner) Next() (tk *Token) { func (scanner *scanner) Next() (tk *Token) {
scanner.prev = scanner.current scanner.prev = scanner.current
tk = scanner.current tk = scanner.current
scanner.current = scanner.fetchNextToken() if scanner.stage != nil {
scanner.current = scanner.stage
scanner.stage = nil
} else {
scanner.current = scanner.fetchNextToken()
}
return tk return tk
} }
@@ -124,6 +140,8 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
tk = scanner.moveOn(SymDoubleStar, ch, next) tk = scanner.moveOn(SymDoubleStar, ch, next)
// } else if next == '/' { // } else if next == '/' {
// tk = self.moveOn(SymClosedComment, ch, next) // tk = self.moveOn(SymClosedComment, ch, next)
} else if next, _ = scanner.peek(); next == '=' {
tk = scanner.moveOn(SymStarEqual, ch, next)
} else { } else {
tk = scanner.makeToken(SymStar, ch) tk = scanner.makeToken(SymStar, ch)
} }
@@ -269,11 +287,11 @@ func (scanner *scanner) fetchNextToken() (tk *Token) {
tk = scanner.makeToken(SymDollar, ch) tk = scanner.makeToken(SymDollar, ch)
} }
case '(': case '(':
if next, _ := scanner.peek(); next == ')' { // if next, _ := scanner.peek(); next == ')' {
tk = scanner.moveOn(SymOpenClosedRound, ch, next) // tk = scanner.moveOn(SymOpenClosedRound, ch, next)
} else { // } else {
tk = scanner.makeToken(SymOpenRound, ch) tk = scanner.makeToken(SymOpenRound, ch)
} // }
case ')': case ')':
tk = scanner.makeToken(SymClosedRound, ch) tk = scanner.makeToken(SymClosedRound, ch)
case '[': case '[':
@@ -440,7 +458,7 @@ func (scanner *scanner) parseNumber(firstCh byte) (tk *Token) {
tk = scanner.makeErrorToken(err) tk = scanner.makeErrorToken(err)
} else { } else {
var value any var value any
err = scanner.sync(err) // TODO: Check this function err = scanner.sync(err) // TODO: Check this function
txt := sb.String() txt := sb.String()
if sym == SymFloat { if sym == SymFloat {
value, err = strconv.ParseFloat(txt, 64) value, err = strconv.ParseFloat(txt, 64)
+20 -3
View File
@@ -132,6 +132,14 @@ func (ctx *SimpleStore) EnumVars(acceptor func(name string) (accept bool)) (varN
return return
} }
func (ctx *SimpleStore) VarCount() int {
return len(ctx.varStore)
}
func (ctx *SimpleStore) DeleteVar(varName string) {
delete(ctx.varStore, varName)
}
func (ctx *SimpleStore) GetFuncInfo(name string) (info ExprFunc, exists bool) { func (ctx *SimpleStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
info, exists = ctx.funcStore[name] info, exists = ctx.funcStore[name]
return return
@@ -141,10 +149,11 @@ func (ctx *SimpleStore) RegisterFuncInfo(info ExprFunc) {
ctx.funcStore[info.Name()], _ = info.(*funcInfo) ctx.funcStore[info.Name()], _ = info.(*funcInfo)
} }
func (ctx *SimpleStore) RegisterFunc(name string, functor Functor, returnType string, params []ExprFuncParam) (err error) { func (ctx *SimpleStore) RegisterFunc(name string, functor Functor, returnType string, params []ExprFuncParam) (exprFunc ExprFunc, err error) {
var info *funcInfo var info *funcInfo
if info, err = newFuncInfo(name, functor, returnType, params); err == nil { if info, err = newFuncInfo(name, functor, returnType, params); err == nil {
ctx.funcStore[name] = info ctx.funcStore[name] = info
exprFunc = info
} }
return return
} }
@@ -163,10 +172,18 @@ func (ctx *SimpleStore) EnumFuncs(acceptor func(name string) (accept bool)) (fun
return return
} }
func (ctx *SimpleStore) Call(name string, args []any) (result any, err error) { func (ctx *SimpleStore) FuncCount() int {
return len(ctx.funcStore)
}
func (ctx *SimpleStore) DeleteFunc(funcName string) {
delete(ctx.funcStore, funcName)
}
func (ctx *SimpleStore) Call(name string, args map[string]any) (result any, err error) {
if info, exists := GetLocalFuncInfo(ctx, name); exists { if info, exists := GetLocalFuncInfo(ctx, name); exists {
functor := info.Functor() functor := info.Functor()
result, err = functor.Invoke(ctx, name, args) result, err = functor.InvokeNamed(ctx, name, args)
} else { } else {
err = fmt.Errorf("unknown function %s()", name) err = fmt.Errorf("unknown function %s()", name)
} }
+3
View File
@@ -68,6 +68,7 @@ const (
SymDoubleDollar // 57: '$$' SymDoubleDollar // 57: '$$'
SymDoubleDot // 58: '..' SymDoubleDot // 58: '..'
SymTripleDot // 59: '...' SymTripleDot // 59: '...'
SymStarEqual // 60: '*='
SymChangeSign SymChangeSign
SymUnchangeSign SymUnchangeSign
SymIdentifier SymIdentifier
@@ -106,6 +107,7 @@ const (
SymKwIn SymKwIn
SymKwInclude SymKwInclude
SymKwNil SymKwNil
SymKwUnset
) )
var keywords map[string]Symbol var keywords map[string]Symbol
@@ -123,5 +125,6 @@ func init() {
"NOT": SymKwNot, "NOT": SymKwNot,
"OR": SymKwOr, "OR": SymKwOr,
"NIL": SymKwNil, "NIL": SymKwNil,
"UNSET": SymKwUnset,
} }
} }
+1 -1
View File
@@ -47,7 +47,7 @@ func TestAddUnknownTokens(t *testing.T) {
wantErr := errors.New(`unexpected token "%"`) wantErr := errors.New(`unexpected token "%"`)
tree := NewAst() tree := NewAst()
if gotErr := tree.addToken(tk0); gotErr != nil && gotErr.Error() != wantErr.Error() { if _, gotErr := tree.addToken(tk0); gotErr != nil && gotErr.Error() != wantErr.Error() {
t.Errorf("err: got <%v>, want <%v>", gotErr, wantErr) t.Errorf("err: got <%v>, want <%v>", gotErr, wantErr)
} }
} }
+3 -2
View File
@@ -21,7 +21,7 @@ func TestFuncBase(t *testing.T) {
/* 7 */ {`int(3.9)`, int64(3), nil}, /* 7 */ {`int(3.9)`, int64(3), nil},
/* 8 */ {`int("432")`, int64(432), nil}, /* 8 */ {`int("432")`, int64(432), nil},
/* 9 */ {`int("1.5")`, nil, `strconv.Atoi: parsing "1.5": invalid syntax`}, /* 9 */ {`int("1.5")`, nil, `strconv.Atoi: parsing "1.5": invalid syntax`},
/* 10 */ {`int("432", 4)`, nil, `int(): too much params -- expected 1, got 2`}, /* 10 */ {`int("432", 4)`, nil, `int(): too many params -- expected 1, got 2`},
/* 11 */ {`int(nil)`, nil, `int(): can't convert nil to int`}, /* 11 */ {`int(nil)`, nil, `int(): can't convert nil to int`},
/* 12 */ {`isInt(2+1)`, true, nil}, /* 12 */ {`isInt(2+1)`, true, nil},
/* 13 */ {`isInt(3.1)`, false, nil}, /* 13 */ {`isInt(3.1)`, false, nil},
@@ -43,7 +43,7 @@ func TestFuncBase(t *testing.T) {
/* 29 */ {`dec(true)`, float64(1), nil}, /* 29 */ {`dec(true)`, float64(1), nil},
/* 30 */ {`dec(true")`, nil, `[1:11] missing string termination "`}, /* 30 */ {`dec(true")`, nil, `[1:11] missing string termination "`},
/* 31 */ {`dec()`, nil, `dec(): too few params -- expected 1, got 0`}, /* 31 */ {`dec()`, nil, `dec(): too few params -- expected 1, got 0`},
/* 32 */ {`dec(1,2,3)`, nil, `dec(): too much params -- expected 1, got 3`}, /* 32 */ {`dec(1,2,3)`, nil, `dec(): too many params -- expected 1, got 3`},
/* 33 */ {`isBool(false)`, true, nil}, /* 33 */ {`isBool(false)`, true, nil},
/* 34 */ {`fract(1|2)`, newFraction(1, 2), nil}, /* 34 */ {`fract(1|2)`, newFraction(1, 2), nil},
/* 35 */ {`fract(12,2)`, newFraction(6, 1), nil}, /* 35 */ {`fract(12,2)`, newFraction(6, 1), nil},
@@ -61,5 +61,6 @@ func TestFuncBase(t *testing.T) {
t.Setenv("EXPR_PATH", ".") t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 10)
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }
+1 -1
View File
@@ -30,7 +30,7 @@ func TestFmt(t *testing.T) {
text := "ciao mondo" text := "ciao mondo"
inputs := []inputType{ inputs := []inputType{
/* 1 */ {fmt.Sprintf(`println("%s")`, text), int64(11), nil}, /* 1 */ {fmt.Sprintf(`builtin "fmt"; println("%s")`, text), int64(11), nil},
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
+1 -1
View File
@@ -19,6 +19,6 @@ func TestFuncImport(t *testing.T) {
t.Setenv("EXPR_PATH", "test-resources") t.Setenv("EXPR_PATH", "test-resources")
// runTestSuiteSpec(t, section, inputs, 1) //runTestSuiteSpec(t, section, inputs, 1)
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }
+30
View File
@@ -0,0 +1,30 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_builtin-iterator.go
package expr
import (
"testing"
)
func TestFuncRun(t *testing.T) {
section := "Builtin-Iterator"
inputs := []inputType{
/* 1 */ {`builtin "iterator"; it=$(1,2,3); run(it)`, nil, nil},
/* 2 */ {`builtin "iterator"; run($(1,2,3), func(index,item){item+10})`, nil, nil},
/* 3 */ {`builtin "iterator"; run($(1,2,3), func(index,item){status=status+item; true}, {"status":0})`, int64(6), nil},
/* 4 */ {`builtin ["iterator", "fmt"]; run($(1,2,3), func(index,item){println(item+10)})`, nil, nil},
/* 5 */ {`builtin "iterator"; run(nil)`, nil, `paramter "iterator" must be an iterator, passed <nil> [nil]`},
/* 6 */ {`builtin "iterator"; run($(1,2,3), nil)`, nil, nil},
/* 7 */ {`builtin "iterator"; run($(1,2,3), func(){1}, "prrr")`, nil, `paramter "vars" must be a dictionary, passed prrr [string]`},
/* 8 */ {`builtin "iterator"; run($(1,2,3), operator=nil)`, nil, nil},
/* 9 */ {`builtin "iterator"; run($(1,2,3), operatorx=nil)`, nil, `run(): unknown param "operatorx"`},
}
//t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 1)
runTestSuite(t, section, inputs)
}
+6 -1
View File
@@ -19,10 +19,15 @@ func TestFuncMathArith(t *testing.T) {
/* 6 */ {`builtin "math.arith"; add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil}, /* 6 */ {`builtin "math.arith"; add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil},
/* 7 */ {`builtin "math.arith"; a=5; b=2; add(a, b*3)`, int64(11), nil}, /* 7 */ {`builtin "math.arith"; a=5; b=2; add(a, b*3)`, int64(11), nil},
/* 8 */ {`builtin "math.arith"; var2="abc"; add(1,2) but var2`, "abc", nil}, /* 8 */ {`builtin "math.arith"; var2="abc"; add(1,2) but var2`, "abc", nil},
/* 9 */ {`builtin "math.arith"; add()`, int64(0), nil},
/* 10 */ {`builtin "math.arith"; mul()`, int64(1), nil},
/* 11 */ {`builtin "math.arith"; add([1,2,3])`, int64(6), nil},
/* 12 */ {`builtin "math.arith"; mul([2,2,3])`, int64(12), nil},
/* 13 */ {`builtin "math.arith"; mul(2,2,3)`, int64(12), nil},
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 1) //runTestSuiteSpec(t, section, inputs, 10)
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }
+1 -1
View File
@@ -30,6 +30,6 @@ func TestFuncOs(t *testing.T) {
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 1) //runTestSuiteSpec(t, section, inputs, 2)
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }
+6 -4
View File
@@ -42,6 +42,8 @@ func runCtxTestSuite(t *testing.T, ctx ExprContext, section string, inputs []inp
failed := 0 failed := 0
for i, input := range inputs { for i, input := range inputs {
// fmt.Printf("%3d: %s\n", i+1, input.source)
good := doTest(t, ctx, section, &input, i+1) good := doTest(t, ctx, section, &input, i+1)
if good { if good {
succeeded++ succeeded++
@@ -91,13 +93,13 @@ func doTest(t *testing.T, ctx ExprContext, section string, input *inputType, cou
eq := reflect.DeepEqual(gotResult, input.wantResult) eq := reflect.DeepEqual(gotResult, input.wantResult)
if !eq /*gotResult != input.wantResult*/ { if !eq /*gotResult != input.wantResult*/ {
t.Errorf("%d: %q -> result = %v [%s], want = %v [%s]", count, input.source, gotResult, TypeName(gotResult), input.wantResult, TypeName(input.wantResult)) t.Errorf("%d: `%s` -> result = %v [%s], want = %v [%s]", count, input.source, gotResult, TypeName(gotResult), input.wantResult, TypeName(input.wantResult))
good = false good = false
} }
if gotErr != wantErr { if gotErr != wantErr {
if wantErr == nil || gotErr == nil || (gotErr.Error() != wantErr.Error()) { if wantErr == nil || gotErr == nil || (gotErr.Error() != wantErr.Error()) {
t.Errorf("%d: %q -> got-err = <%v>, expected-err = <%v>", count, input.source, gotErr, wantErr) t.Errorf("%d: %s -> got-err = <%v>, expected-err = <%v>", count, input.source, gotErr, wantErr)
good = false good = false
} }
} }
@@ -106,8 +108,8 @@ func doTest(t *testing.T, ctx ExprContext, section string, input *inputType, cou
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 -- `%s` --> %v", section, n, source, wantResult)
} else { } else {
t.Logf("[-]%s nr %3d -- %q --> %v", section, n, source, wantErr) t.Logf("[-]%s nr %3d -- `%s` --> %v", section, n, source, wantErr)
} }
} }
+7 -2
View File
@@ -22,7 +22,7 @@ func TestDictParser(t *testing.T) {
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`{}`, map[any]any{}, nil}, /* 1 */ {`{}`, map[any]any{}, nil},
/* 2 */ {`{123}`, nil, errors.New(`[1:6] expected ":", got "}"`)}, /* 2 */ {`{123}`, nil, errors.New("[1:6] expected `:`, got `}`")},
/* 3 */ {`{1:"one",2:"two",3:"three"}`, map[int64]any{int64(1): "one", int64(2): "two", int64(3): "three"}, nil}, /* 3 */ {`{1:"one",2:"two",3:"three"}`, map[int64]any{int64(1): "one", int64(2): "two", int64(3): "three"}, nil},
/* 4 */ {`{1:"one",2:"two",3:"three"}[3]`, "three", nil}, /* 4 */ {`{1:"one",2:"two",3:"three"}[3]`, "three", nil},
/* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil}, /* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil},
@@ -32,7 +32,12 @@ func TestDictParser(t *testing.T) {
/* 9 */ {`D={"a":1, "b":2}; D["z"]=9; D`, map[any]any{"z": 9, "a": 1, "b": 2}, nil}, /* 9 */ {`D={"a":1, "b":2}; D["z"]=9; D`, map[any]any{"z": 9, "a": 1, "b": 2}, nil},
/* 10 */ {`D={"a":1, "b":2}; D[nil]=9`, nil, errors.New(`[1:21] index/key is nil`)}, /* 10 */ {`D={"a":1, "b":2}; D[nil]=9`, nil, errors.New(`[1:21] index/key is nil`)},
/* 11 */ {`D={"a":1, "b":2}; D["a"]`, int64(1), nil}, /* 11 */ {`D={"a":1, "b":2}; D["a"]`, int64(1), nil},
} /* 12 */ {`m={
"a":1,
//"b":2,
"c":3
}`, map[any]any{"a": 1, "c": 3}, nil},
}
succeeded := 0 succeeded := 0
failed := 0 failed := 0
+15 -4
View File
@@ -17,10 +17,21 @@ func TestExpr(t *testing.T) {
/* 3 */ {`builtin "os.file"; f=fileOpen("test-file.txt"); line=fileReadText(f); fileClose(f); line`, "uno", nil}, /* 3 */ {`builtin "os.file"; f=fileOpen("test-file.txt"); line=fileReadText(f); fileClose(f); line`, "uno", nil},
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil}, /* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
/* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil}, /* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil},
/* 6 */ {` /* 6 */ {`a=3; a+=1`, int64(4), nil},
/* 7 */ {`a=3; a-=1`, int64(2), nil},
/* 8 */ {`a=3; a*=2`, int64(6), nil},
/* 9 */ {`v=[10,20,30]; v[0]+=1; v[0]`, int64(11), nil},
/* 10 */ {`r={"a":10}; r["a"]+=1; r["a"]`, int64(11), nil},
/* 11 */ {`a=3; a/=2`, nil, `[1:8] left operand of "=" must be a variable or a collection's item`},
/* 12 */ {`*=2`, nil, `[1:2] left operand of "*=" must be a variable or a variable expression`},
/* 13 */ {`a=3; a*=2+1; a`, int64(9), nil},
/* 14 */ {`a=3; (a*=2)+1; a`, int64(6), nil},
/* 15 */ {`a=3; a*=2)+1; a`, nil, `[1:11] unexpected token ")"`},
/* 16 */ {`v=[2]; a=1; v[a-=1]=5; v[0]`, int64(5), nil},
/* 17 */ {`
ds={ ds={
"init":func(end){@end=end; @current=0 but true}, "init":func(@end){@current=0 but true},
"current":func(){current}, //"current":func(){current},
"next":func(){ "next":func(){
((next=current+1) <= end) ? [true] {@current=next but current} :: {nil} ((next=current+1) <= end) ? [true] {@current=next but current} :: {nil}
} }
@@ -32,6 +43,6 @@ func TestExpr(t *testing.T) {
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 3) // runTestSuiteSpec(t, section, inputs, 17)
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }
+11 -6
View File
@@ -24,23 +24,29 @@ func TestFuncs(t *testing.T) {
/* 11 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil}, /* 11 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 12 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil}, /* 12 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
/* 13 */ {`two=func(){2}; four=func(f){f()+f()}; four(two)`, int64(4), nil}, /* 13 */ {`two=func(){2}; four=func(f){f()+f()}; four(two)`, int64(4), nil},
/* 14 */ {`two=func(){2}; two(123)`, nil, `two(): too much params -- expected 0, got 1`}, /* 14 */ {`two=func(){2}; two(123)`, nil, `two(): too many params -- expected 0, got 1`},
/* 15 */ {`f=func(x,n=2){x+n}; f(3)`, int64(5), nil}, /* 15 */ {`f=func(x,n=2){x+n}; f(3)`, int64(5), nil},
/* 16 */ {`f=func(x,n=2,y){x+n}`, nil, `[1:16] can't mix default and non-default parameters`}, /* 16 */ {`f=func(x,n=2,y){x+n}`, nil, `[1:16] can't mix default and non-default parameters`},
/* 17 */ {`f=func(x,n){1}; f(3,4,)`, nil, `[1:24] expected "function-param-value", got ")"`}, /* 17 */ {`f=func(x,n){1}; f(3,4,)`, nil, "[1:24] expected `function-param-value`, got `)`"},
/* 18 */ {`factory=func(base){func(){@base=base+1}}; inc10=factory(10); inc5=factory(5); inc10(); inc5(); inc10()`, int64(12), nil}, /* 18 */ {`factory=func(base){func(){@base=base+1}}; inc10=factory(10); inc5=factory(5); inc10(); inc5(); inc10()`, int64(12), nil},
/* 19 */ {`f=func(a,y=1,z="sos"){}; string(f)`, `f(a, y=1, z="sos"):any{}`, nil}, /* 19 */ {`f=func(a,y=1,z="sos"){}; string(f)`, `f(a, y=1, z="sos"):any{}`, nil},
// /* 20 */ {`m={}; m["f"]=func(){3}; m["f"]()`, int64(3), nil}, /* 20 */ {`f=func(a,b){a*2+b}; f(1,10)`, int64(12), nil},
/* 21 */ {`f=func(a,b){a*2+b}; f(a=2,b=1)`, int64(5), nil},
/* 22 */ {`f=func(a,b){a*2+b}; f(b=2,a=1)`, int64(4), nil},
/* 23 */ {`f=func(a=10,b=10){a*2+b}; f(b=1)`, int64(21), nil},
/* 24 */ {`f=func(a,b){a*2+b}; f(a=1,2)`, nil, `f(): positional param nr 2 not allowed after named params`},
// /* 20 */ {`a=[func(){3}]; a[0]()`, int64(3), nil},
// /* 20 */ {`m={}; m["f"]=func(){3}; m["f"]()`, int64(3), nil},
// /* 18 */ {`f=func(a){a*2}`, nil, errors.New(`[1:24] expected "function-param-value", got ")"`)}, // /* 18 */ {`f=func(a){a*2}`, nil, errors.New(`[1:24] expected "function-param-value", got ")"`)},
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 20) //runTestSuiteSpec(t, section, inputs, 19)
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }
func dummy(ctx ExprContext, name string, args []any) (result any, err error) { func dummy(ctx ExprContext, name string, args map[string]any) (result any, err error) {
return return
} }
@@ -53,7 +59,6 @@ func TestFunctionToStringSimple(t *testing.T) {
} }
} }
func TestFunctionGetFunc(t *testing.T) { func TestFunctionGetFunc(t *testing.T) {
source := NewGolangFunctor(dummy) source := NewGolangFunctor(dummy)
want := ExprFunc(nil) want := ExprFunc(nil)
+40 -39
View File
@@ -5,50 +5,51 @@
package expr package expr
import ( import (
"fmt"
"testing" "testing"
) )
func subtract(ctx ExprContext, name string, args []any) (result any, err error) { // TODO The new function param model does not allow this kind of test
if len(args) != 2 { // ------------------------------------------------------------------
err = fmt.Errorf("%s(): requires exactly two arguments", name) // func subtract(ctx ExprContext, name string, args map[string]any) (result any, err error) {
return // if len(args) != 2 {
} // err = fmt.Errorf("%s(): requires exactly two arguments", name)
x, xok := args[0].(int64) // return
y, yok := args[1].(int64) // }
if xok && yok { // x, xok := args[0].(int64)
result = x - y // y, yok := args[1].(int64)
} else { // if xok && yok {
err = fmt.Errorf("expected integer (int64) arguments, got %T and %T values", x, y) // result = x - y
} // } else {
return // err = fmt.Errorf("expected integer (int64) arguments, got %T and %T values", x, y)
} // }
// return
// }
func TestEvalStringA(t *testing.T) { // func TestEvalStringA(t *testing.T) {
source := `a + b * subtract(4,2)` // source := `a + b * subtract(4,2)`
args := []Arg{ // args := []Arg{
{"a", uint8(1)}, // {"a", uint8(1)},
{"b", int8(2)}, // {"b", int8(2)},
{"subtract", FuncTemplate(subtract)}, // {"subtract", FuncTemplate2(subtract)},
// force coverage // // force coverage
{"a16", uint16(1)}, // {"a16", uint16(1)},
{"b16", int16(2)}, // {"b16", int16(2)},
{"a32", uint32(1)}, // {"a32", uint32(1)},
{"b32", int32(2)}, // {"b32", int32(2)},
{"a64", uint64(1)}, // {"a64", uint64(1)},
{"b64", int64(2)}, // {"b64", int64(2)},
{"f32", float32(1.0)}, // {"f32", float32(1.0)},
{"f64", float64(1.0)}, // {"f64", float64(1.0)},
} // }
wantResult := int64(5) // wantResult := int64(5)
gotResult, gotErr := EvalStringA(source, args...) // gotResult, gotErr := EvalStringA(source, args...)
if value, ok := gotResult.(int64); ok && value != wantResult { // if value, ok := gotResult.(int64); ok && value != wantResult {
t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult) // t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult)
t.Errorf("Error: %v", gotErr) // t.Errorf("Error: %v", gotErr)
} // }
} // }
func TestEvalString(t *testing.T) { func TestEvalString(t *testing.T) {
@@ -68,7 +69,7 @@ func TestEvalString(t *testing.T) {
// force coverage // force coverage
ctx.GetFuncInfo("dummy") ctx.GetFuncInfo("dummy")
ctx.Call("dummy", []any{}) ctx.Call("dummy", map[string]any{})
source := `a + b * f` source := `a + b * f`
+7 -7
View File
@@ -15,7 +15,7 @@ func TestNewListIterator(t *testing.T) {
if item, err := it.Next(); err != nil { if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err) t.Errorf("error: %v", err)
} else if item != "b" { } else if item != "b" {
t.Errorf("expcted %q, got %q", "b", item) t.Errorf("expected %q, got %q", "b", item)
} else { } else {
t.Logf("Next: %v", item) t.Logf("Next: %v", item)
} }
@@ -27,7 +27,7 @@ func TestNewListIterator2(t *testing.T) {
if item, err := it.Next(); err != nil { if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err) t.Errorf("error: %v", err)
} else if item != "d" { } else if item != "d" {
t.Errorf("expcted %q, got %q", "d", item) t.Errorf("expected %q, got %q", "d", item)
} else { } else {
t.Logf("Next: %v", item) t.Logf("Next: %v", item)
} }
@@ -39,7 +39,7 @@ func TestNewListIterator3(t *testing.T) {
if item, err := it.Next(); err != nil { if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err) t.Errorf("error: %v", err)
} else if item != "b" { } else if item != "b" {
t.Errorf("expcted %q, got %q", "b", item) t.Errorf("expected %q, got %q", "b", item)
} else { } else {
t.Logf("Next: %v", item) t.Logf("Next: %v", item)
} }
@@ -51,7 +51,7 @@ func TestNewIterList2(t *testing.T) {
if item, err := it.Next(); err != nil { if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err) t.Errorf("error: %v", err)
} else if item != "a" { } else if item != "a" {
t.Errorf("expcted %q, got %q", "a", item) t.Errorf("expected %q, got %q", "a", item)
} else { } else {
t.Logf("Next: %v", item) t.Logf("Next: %v", item)
} }
@@ -63,7 +63,7 @@ func TestNewIterList3(t *testing.T) {
if item, err := it.Next(); err != nil { if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err) t.Errorf("error: %v", err)
} else if item != "a" { } else if item != "a" {
t.Errorf("expcted %q, got %q", "a", item) t.Errorf("expected %q, got %q", "a", item)
} else { } else {
t.Logf("Next: %v", item) t.Logf("Next: %v", item)
} }
@@ -83,7 +83,7 @@ func TestNewIterList5(t *testing.T) {
if item, err := it.Next(); err != nil { if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err) t.Errorf("error: %v", err)
} else if item != "123" { } else if item != "123" {
t.Errorf("expcted %q, got %q", "123", item) t.Errorf("expected %q, got %q", "123", item)
} else { } else {
t.Logf("Next: %v", item) t.Logf("Next: %v", item)
} }
@@ -96,7 +96,7 @@ func TestNewIterList6(t *testing.T) {
if item, err := it.Next(); err != nil { if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err) t.Errorf("error: %v", err)
} else if item != "a" { } else if item != "a" {
t.Errorf("expcted %q, got %q", "a", item) t.Errorf("expected %q, got %q", "a", item)
} else { } else {
t.Logf("Next: %v", item) t.Logf("Next: %v", item)
} }
+9 -5
View File
@@ -9,10 +9,10 @@ import "testing"
func TestIteratorParser(t *testing.T) { func TestIteratorParser(t *testing.T) {
section := "Iterator" section := "Iterator"
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`include "test-resources/iterator.expr"; it=$(ds,3); ()it`, int64(0), nil}, /* 1 */ {`include "test-resources/iterator.expr"; it=$(ds,3); ^it`, int64(0), nil},
/* 2 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++`, int64(1), nil}, /* 2 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++`, int64(1), nil},
/* 3 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; #it`, int64(2), nil}, /* 3 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; #it`, int64(3), nil},
/* 4 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; it.reset; ()it`, int64(0), nil}, /* 4 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; it.reset; ^it`, int64(0), nil},
/* 5 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); add(it)`, int64(6), nil}, /* 5 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); add(it)`, int64(6), nil},
/* 6 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); mul(it)`, int64(0), nil}, /* 6 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); mul(it)`, int64(0), nil},
/* 7 */ {`builtin "math.arith"; include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); mul(it)`, int64(12000), nil}, /* 7 */ {`builtin "math.arith"; include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); mul(it)`, int64(12000), nil},
@@ -20,9 +20,13 @@ func TestIteratorParser(t *testing.T) {
/* 9 */ {`include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); it.clean`, true, nil}, /* 9 */ {`include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); it.clean`, true, nil},
/* 10 */ {`it=$(1,2,3); it++`, int64(1), nil}, /* 10 */ {`it=$(1,2,3); it++`, int64(1), nil},
/* 11 */ {`it=$(1,2,3); it++; it.reset; it++`, int64(1), nil}, /* 11 */ {`it=$(1,2,3); it++; it.reset; it++`, int64(1), nil},
// /* 12 */ {`include "test-resources/filter.expr"; it=$(ds,10); it++`, int64(1), nil}, /* 12 */ {`it=$([1,2,3,4],1); it++`, int64(2), nil},
/* 13 */ {`it=$([1,2,3,4],1,3); it++; it++;`, int64(3), nil},
/* 14 */ {`it=$([1,2,3,4],1,3,2); it++; it++;`, int64(4), nil},
/* 15 */ {`it=$([1,2,3,4],1,2,2); it++; it++;`, nil, `EOF`},
/* 16 */ {`include "test-resources/filter.expr"; it=$(ds,10); it++`, int64(2), nil},
} }
//runTestSuiteSpec(t, section, inputs, 12) // runTestSuiteSpec(t, section, inputs, 11)
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }
+23
View File
@@ -0,0 +1,23 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_operator_test.go
package expr
import (
"testing"
)
func TestOperator(t *testing.T) {
section := "Operator"
inputs := []inputType{
/* 1 */ {`a=1; unset "a"; a`, nil, `undefined variable or function "a"`},
/* 2 */ {`a=1; unset ["a", "b"]`, int64(1), nil},
/* 3 */ {`f=func(){3}; unset "f()"`, int64(1), nil},
}
// t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 3)
runTestSuite(t, section, inputs)
}
+3 -3
View File
@@ -128,8 +128,8 @@ func TestGeneralParser(t *testing.T) {
/* 114 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil}, /* 114 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
/* 115 */ {`nil`, nil, nil}, /* 115 */ {`nil`, nil, nil},
/* 116 */ {`null`, nil, `undefined variable or function "null"`}, /* 116 */ {`null`, nil, `undefined variable or function "null"`},
/* 117 */ {`{"key"}`, nil, `[1:8] expected ":", got "}"`}, /* 117 */ {`{"key"}`, nil, "[1:8] expected `:`, got `}`"},
/* 118 */ {`{"key":}`, nil, `[1:9] expected "dictionary-value", got "}"`}, /* 118 */ {`{"key":}`, nil, "[1:9] expected `dictionary-value`, got `}`"},
/* 119 */ {`{}`, &DictType{}, nil}, /* 119 */ {`{}`, &DictType{}, nil},
/* 120 */ {`v=10; v++; v`, int64(11), nil}, /* 120 */ {`v=10; v++; v`, int64(11), nil},
/* 121 */ {`1+1|2+0.5`, float64(2), nil}, /* 121 */ {`1+1|2+0.5`, float64(2), nil},
@@ -144,6 +144,6 @@ func TestGeneralParser(t *testing.T) {
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 102) //runTestSuiteSpec(t, section, inputs, 130)
runTestSuite(t, section, inputs) runTestSuite(t, section, inputs)
} }
+9 -2
View File
@@ -9,8 +9,15 @@ import (
) )
func _TestImportPlugin(t *testing.T) { func _TestImportPlugin(t *testing.T) {
if err := importPlugin([]string{"test-resources"}, "json"); err != nil { t.Setenv("PLUGINS", "${HOME}/go/src/git.portale-stac.it/go")
t.Errorf("importPlugin() failed: %v", err) t.Setenv("EXPR_PLUGIN_PATH","${PLUGINS}/expr-json-plugin:${PLUGINS}/expr-csv-plugin")
gotCount, gotErr := importPluginFromSearchPath("json")
if gotCount != 1 {
t.Errorf("Import count: got=%d, want=1", gotCount)
}
if gotErr != nil {
t.Errorf("importPlugin() failed: %v", gotErr)
} }
} }
+20 -1
View File
@@ -26,7 +26,7 @@ const (
priSign priSign
priFact priFact
priIterValue priIterValue
priCoalesce priDefault
priIncDec priIncDec
priDot priDot
priValue priValue
@@ -53,6 +53,25 @@ type term struct {
evalFunc evalFuncType evalFunc evalFuncType
} }
func (s *term) Clone() (d *term) {
var children []*term
if s.children != nil {
children = make([]*term, len(s.children))
for i, c := range s.children {
children[i] = c.Clone()
}
}
d = &term{
tk: *s.tk.Clone(),
parent: s.parent,
children: children,
position: s.position,
priority: s.priority,
evalFunc: s.evalFunc,
}
return
}
func (term *term) String() string { func (term *term) String() string {
var sb strings.Builder var sb strings.Builder
term.toString(&sb) term.toString(&sb)
+15
View File
@@ -0,0 +1,15 @@
ds={
"init":func(end){@end=end; @current=0 but true},
"current":func(){current},
"next":func(){
((next=current+1) <= end) ? [true] {@current=next but current} :: {nil}
}
, "filter":func(item,index) { item > 0 }
, "map": func(item,index) { item * 2 }
}
/* Example
;
it=$(ds,3);
add(it)
*/
+11 -3
View File
@@ -28,7 +28,7 @@ func (tk *Token) DevString() string {
func (tk *Token) String() string { func (tk *Token) String() string {
if tk.Value != nil { if tk.Value != nil {
if s, ok := tk.Value.(string); ok { if s, ok := tk.Value.(string); ok {
return fmt.Sprintf("%q", s) return s //fmt.Sprintf("%q", s)
} else { } else {
return fmt.Sprintf("%v", tk.Value) return fmt.Sprintf("%v", tk.Value)
} }
@@ -51,6 +51,10 @@ func NewErrorToken(row, col int, err error) *Token {
return NewValueToken(row, col, SymError, fmt.Sprintf("[%d:%d]", row, col), err) return NewValueToken(row, col, SymError, fmt.Sprintf("[%d:%d]", row, col), err)
} }
func (tk *Token) Clone() (c *Token) {
return NewValueToken(tk.row, tk.col, tk.Sym, tk.source, tk.Value)
}
func (tk *Token) IsEos() bool { func (tk *Token) IsEos() bool {
return tk.Sym == SymEos return tk.Sym == SymEos
} }
@@ -67,6 +71,10 @@ func (tk *Token) IsOneOf(termSymbols []Symbol) bool {
return termSymbols != nil && slices.Index(termSymbols, tk.Sym) >= 0 return termSymbols != nil && slices.Index(termSymbols, tk.Sym) >= 0
} }
func (tk *Token) IsOneOfA(termSymbols ...Symbol) bool {
return slices.Index(termSymbols, tk.Sym) >= 0
}
func (tk *Token) IsSymbol(sym Symbol) bool { func (tk *Token) IsSymbol(sym Symbol) bool {
return tk.Sym == sym return tk.Sym == sym
} }
@@ -91,11 +99,11 @@ func (tk *Token) Errors(msg string) (err error) {
} }
func (tk *Token) ErrorExpectedGot(symbol string) (err error) { func (tk *Token) ErrorExpectedGot(symbol string) (err error) {
err = fmt.Errorf("[%d:%d] expected %q, got %q", tk.row, tk.col, symbol, tk) err = fmt.Errorf("[%d:%d] expected `%s`, got `%s`", tk.row, tk.col, symbol, tk)
return return
} }
func (tk *Token) ErrorExpectedGotString(symbol, got string) (err error) { func (tk *Token) ErrorExpectedGotString(symbol, got string) (err error) {
err = fmt.Errorf("[%d:%d] expected %q, got %q", tk.row, tk.col, symbol, got) err = fmt.Errorf("[%d:%d] expected `%s`, got `%s`", tk.row, tk.col, symbol, got)
return return
} }
+38
View File
@@ -6,7 +6,11 @@ package expr
import ( import (
"fmt" "fmt"
"os"
"os/user"
"path"
"reflect" "reflect"
"strings"
) )
func IsString(v any) (ok bool) { func IsString(v any) (ok bool) {
@@ -221,3 +225,37 @@ func ForAll[T, V any](ts []T, fn func(T) V) []V {
} }
return result return result
} }
func ExpandPath(sourcePath string) (expandedPath string, err error) {
for expandedPath = os.ExpandEnv(sourcePath); expandedPath != sourcePath; expandedPath = os.ExpandEnv(sourcePath) {
sourcePath = expandedPath
}
if strings.HasPrefix(sourcePath, "~") {
var home, userName, remainder string
slashPos := strings.IndexRune(sourcePath, '/')
if slashPos > 0 {
userName = sourcePath[1:slashPos]
remainder = sourcePath[slashPos:]
} else {
userName = sourcePath[1:]
}
if len(userName) == 0 {
home, err = os.UserHomeDir()
if err != nil {
return
}
} else {
var userInfo *user.User
userInfo, err = user.Lookup(userName)
if err != nil {
return
}
home = userInfo.HomeDir
}
expandedPath = path.Join(home, remainder)
}
return
}