Compare commits

..

3 Commits

27 changed files with 642 additions and 422 deletions
+3 -2
View File
@@ -127,8 +127,9 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
} }
} }
if err == nil { if err == nil {
result, err = self.root.compute(ctx) if result, err = self.root.compute(ctx); err == nil {
ctx.UnsafeSetVar(ControlLastResult, result) ctx.UnsafeSetVar(ControlLastResult, result)
}
} }
// } else { // } else {
// err = errors.New("empty expression") // err = errors.New("empty expression")
+16 -8
View File
@@ -8,32 +8,40 @@ import (
"fmt" "fmt"
) )
func errTooFewParams(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("too few params -- expected %d or more, got %d", minArgs, argCount) err = fmt.Errorf("%s(): too few params -- expected %d or more, got %d", funcName, minArgs, argCount)
} else { } else {
err = fmt.Errorf("too few params -- expected %d, got %d", minArgs, argCount) err = fmt.Errorf("%s(): too few params -- expected %d, got %d", funcName, minArgs, argCount)
} }
return return
} }
func errTooMuchParams(maxArgs, argCount int) (err error) { func errTooMuchParams(funcName string, maxArgs, argCount int) (err error) {
err = fmt.Errorf("too much params -- expected %d, got %d", maxArgs, argCount) err = fmt.Errorf("%s(): too much params -- expected %d, got %d", funcName, maxArgs, argCount)
return return
} }
// --- General errors // --- General errors
func errCantConvert(funcName string, value any, kind string) error { func errCantConvert(funcName string, value any, kind string) error {
return fmt.Errorf("%s() can't convert %T to %s", funcName, value, kind) if typer, ok := value.(Typer); ok {
return fmt.Errorf("%s(): can't convert %s to %s", funcName, typer.TypeName(), kind)
} else {
return fmt.Errorf("%s(): can't convert %T to %s", funcName, value, kind)
}
} }
func errExpectedGot(funcName string, kind string, value any) error { func errExpectedGot(funcName string, kind string, value any) error {
return fmt.Errorf("%s() expected %s, got %T (%v)", funcName, kind, value, value) return fmt.Errorf("%s() expected %s, got %T (%v)", funcName, kind, value, value)
} }
func errDivisionByZero(funcName string) error { func errFuncDivisionByZero(funcName string) error {
return fmt.Errorf("%s() division by zero", funcName) return fmt.Errorf("%s(): division by zero", funcName)
}
func errDivisionByZero() error {
return fmt.Errorf("division by zero")
} }
// --- Parameter errors // --- Parameter errors
+2
View File
@@ -9,6 +9,7 @@ type Functor interface {
Invoke(ctx ExprContext, name string, args []any) (result any, err error) Invoke(ctx ExprContext, name string, args []any) (result any, err error)
SetFunc(info ExprFunc) SetFunc(info ExprFunc)
GetFunc() ExprFunc GetFunc() ExprFunc
GetParams() []ExprFuncParam
} }
// ---- Function Param Info // ---- Function Param Info
@@ -34,6 +35,7 @@ type ExprFunc interface {
// ----Expression Context // ----Expression Context
type ExprContext interface { type ExprContext interface {
Clone() ExprContext Clone() ExprContext
Merge(ctx ExprContext)
GetVar(varName string) (value any, exists bool) GetVar(varName string) (value any, exists bool)
SetVar(varName string, value any) SetVar(varName string, value any)
UnsafeSetVar(varName string, value any) UnsafeSetVar(varName string, value any)
+1 -1
View File
@@ -129,7 +129,7 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
if den, ok = args[1].(int64); !ok { if den, ok = args[1].(int64); !ok {
err = errExpectedGot(name, "integer", args[1]) err = errExpectedGot(name, "integer", args[1])
} else if den == 0 { } else if den == 0 {
err = errDivisionByZero(name) err = errFuncDivisionByZero(name)
} }
} }
if err == nil { if err == nil {
+2 -2
View File
@@ -168,11 +168,11 @@ func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
func ImportMathFuncs(ctx ExprContext) { func ImportMathFuncs(ctx ExprContext) {
ctx.RegisterFunc("add", &golangFunctor{f: addFunc}, typeNumber, []ExprFuncParam{ ctx.RegisterFunc("add", &golangFunctor{f: addFunc}, typeNumber, []ExprFuncParam{
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 0), newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, int64(0)),
}) })
ctx.RegisterFunc("mul", &golangFunctor{f: mulFunc}, typeNumber, []ExprFuncParam{ ctx.RegisterFunc("mul", &golangFunctor{f: mulFunc}, typeNumber, []ExprFuncParam{
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 1), newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, int64(1)),
}) })
} }
+81 -52
View File
@@ -20,6 +20,14 @@ type osWriter struct {
writer *bufio.Writer writer *bufio.Writer
} }
func (h *osWriter) TypeName() string {
return "osWriter"
}
func (h *osWriter) String() string {
return "writer"
}
func (h *osWriter) getFile() *os.File { func (h *osWriter) getFile() *os.File {
return h.fh return h.fh
} }
@@ -29,66 +37,73 @@ type osReader struct {
reader *bufio.Reader reader *bufio.Reader
} }
func (h *osReader) TypeName() string {
return "osReader"
}
func (h *osReader) String() string {
return "reader"
}
func (h *osReader) getFile() *os.File { func (h *osReader) getFile() *os.File {
return h.fh return h.fh
} }
func createFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func errMissingFilePath(funcName string) error {
var filePath string return fmt.Errorf("%s(): missing or invalid file path", funcName)
if len(args) > 0 { }
filePath, _ = args[0].(string)
}
if len(filePath) > 0 { func errInvalidFileHandle(funcName string, v any) error {
if v != nil {
return fmt.Errorf("%s(): invalid file handle %v [%T]", funcName, v, v)
} else {
return fmt.Errorf("%s(): invalid file handle", funcName)
}
}
func createFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if filePath, ok := args[0].(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)}
} }
} else { } else {
err = fmt.Errorf("%s(): missing the file path", name) err = errMissingFilePath("createFile")
} }
return return
} }
func openFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func openFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var filePath string if filePath, ok := args[0].(string); ok && len(filePath) > 0 {
if len(args) > 0 {
filePath, _ = args[0].(string)
}
if 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)}
} }
} else { } else {
err = fmt.Errorf("%s(): missing the file path", name) err = errMissingFilePath("openFile")
} }
return return
} }
func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var filePath string if filePath, ok := args[0].(string); ok && len(filePath) > 0 {
if len(args) > 0 {
filePath, _ = args[0].(string)
}
if 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)}
} }
} else { } else {
err = fmt.Errorf("%s(): missing the file path", name) err = errMissingFilePath("openFile")
} }
return return
} }
func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle var handle osHandle
var invalidFileHandle any
var ok bool
if len(args) > 0 { if handle, ok = args[0].(osHandle); !ok {
handle, _ = args[0].(osHandle) invalidFileHandle = args[0]
} }
if handle != nil { if handle != nil {
@@ -96,12 +111,14 @@ func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err er
if w, ok := handle.(*osWriter); ok { if w, ok := handle.(*osWriter); ok {
err = w.writer.Flush() err = w.writer.Flush()
} }
if err == nil { if err == nil {
err = fh.Close() err = fh.Close()
} }
} }
} else { }
err = fmt.Errorf("%s(): invalid file handle", name) if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle("closeFileFunc", handle)
} }
result = err == nil result = err == nil
return return
@@ -109,51 +126,63 @@ func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err er
func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle var handle osHandle
var invalidFileHandle any
var ok bool
if len(args) > 0 { if handle, ok = args[0].(osHandle); !ok {
handle, _ = args[0].(osHandle) invalidFileHandle = args[0]
} }
if handle != nil { if handle != nil {
if fh := handle.getFile(); fh != nil { if w, ok := handle.(*osWriter); ok {
if w, ok := handle.(*osWriter); ok { result, err = fmt.Fprint(w.writer, args[1:]...)
result, err = fmt.Fprint(w.writer, args[1:]...) } else {
} invalidFileHandle = handle
} }
} }
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle("writeFileFunc", invalidFileHandle)
}
return return
} }
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle var handle osHandle
var invalidFileHandle any
var ok bool
result = nil result = nil
if len(args) > 0 { if handle, ok = args[0].(osHandle); !ok || args[0] == nil {
handle, _ = args[0].(osHandle) invalidFileHandle = args[0]
} }
if handle != nil { if handle != nil {
if fh := handle.getFile(); fh != 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 len(args) > 1 { limit = s[0]
if s, ok := args[1].(string); ok && len(s) > 0 { }
limit = s[0]
} if v, err = r.reader.ReadString(limit); err == nil {
} if len(v) > 0 && v[len(v)-1] == limit {
if v, err = r.reader.ReadString(limit); err == nil { result = v[0 : len(v)-1]
if len(v) > 0 && v[len(v)-1] == limit { } else {
result = v[0 : len(v)-1] result = v
} else {
result = v
}
}
if err == io.EOF {
err = nil
} }
} }
if err == io.EOF {
err = nil
}
} else {
invalidFileHandle = handle
} }
} }
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle("readFileFunc", invalidFileHandle)
}
return return
} }
@@ -173,7 +202,7 @@ func ImportOsFuncs(ctx ExprContext) {
}) })
ctx.RegisterFunc("readFile", newGolangFunctor(readFileFunc), typeString, []ExprFuncParam{ ctx.RegisterFunc("readFile", newGolangFunctor(readFileFunc), typeString, []ExprFuncParam{
newFuncParam(typeHandle), newFuncParam(typeHandle),
newFuncParamFlagDef("limitCh", pfOptional, "\\n"), newFuncParamFlagDef("limitCh", pfOptional, "\n"),
}) })
ctx.RegisterFunc("closeFile", newGolangFunctor(closeFileFunc), typeBoolean, []ExprFuncParam{ ctx.RegisterFunc("closeFile", newGolangFunctor(closeFileFunc), typeBoolean, []ExprFuncParam{
newFuncParam(typeHandle), newFuncParam(typeHandle),
+25 -26
View File
@@ -65,19 +65,19 @@ func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error
if source, ok = args[0].(string); !ok { if source, ok = args[0].(string); !ok {
return nil, errWrongParamType(name, paramSource, typeString, args[0]) return nil, errWrongParamType(name, paramSource, typeString, args[0])
} }
if len(args) > 1 {
if start, err = toInt(args[1], name+"()"); err != nil { if start, err = toInt(args[1], name+"()"); err != nil {
return return
}
if len(args) > 2 {
if count, err = toInt(args[2], name+"()"); err != nil {
return
}
}
if start < 0 {
start = len(source) + start
}
} }
if count, err = toInt(args[2], name+"()"); err != nil {
return
}
if start < 0 {
start = len(source) + start
}
if count < 0 { if count < 0 {
count = len(source) - start count = len(source) - start
} }
@@ -152,18 +152,17 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
if source, ok = args[0].(string); !ok { if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0]) return result, errWrongParamType(name, paramSource, typeString, args[0])
} }
if len(args) >= 2 {
if sep, ok = args[1].(string); !ok { if sep, ok = args[1].(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 %T (%v)", args[1], args[1])
}
if len(args) >= 3 {
if count64, ok := args[2].(int64); ok { // TODO replace type assertion with toInt()
count = int(count64)
} else {
return nil, fmt.Errorf("part count must be integer, got %T (%v)", args[2], args[2])
}
}
} }
if count64, ok := args[2].(int64); ok { // TODO replace type assertion with toInt()
count = int(count64)
} else {
return nil, fmt.Errorf("part count must be integer, got %T (%v)", args[2], args[2])
}
if count > 0 { if count > 0 {
parts = strings.SplitN(source, sep, count) parts = strings.SplitN(source, sep, count)
} else if count < 0 { } else if count < 0 {
@@ -190,14 +189,14 @@ func ImportStringFuncs(ctx ExprContext) {
ctx.RegisterFunc("subStr", newGolangFunctor(subStrFunc), typeString, []ExprFuncParam{ ctx.RegisterFunc("subStr", newGolangFunctor(subStrFunc), typeString, []ExprFuncParam{
newFuncParam(paramSource), newFuncParam(paramSource),
newFuncParamFlagDef(paramStart, pfOptional, 0), newFuncParamFlagDef(paramStart, pfOptional, int64(0)),
newFuncParamFlagDef(paramCount, pfOptional, -1), newFuncParamFlagDef(paramCount, pfOptional, int64(-1)),
}) })
ctx.RegisterFunc("splitStr", newGolangFunctor(splitStrFunc), "list of "+typeString, []ExprFuncParam{ ctx.RegisterFunc("splitStr", newGolangFunctor(splitStrFunc), "list of "+typeString, []ExprFuncParam{
newFuncParam(paramSource), newFuncParam(paramSource),
newFuncParamFlagDef(paramSeparator, pfOptional, ""), newFuncParamFlagDef(paramSeparator, pfOptional, ""),
newFuncParamFlagDef(paramCount, pfOptional, -1), newFuncParamFlagDef(paramCount, pfOptional, int64(-1)),
}) })
ctx.RegisterFunc("trimStr", newGolangFunctor(trimStrFunc), typeString, []ExprFuncParam{ ctx.RegisterFunc("trimStr", newGolangFunctor(trimStrFunc), typeString, []ExprFuncParam{
+26 -10
View File
@@ -26,6 +26,14 @@ func (functor *baseFunctor) ToString(opt FmtOpt) (s string) {
return s return s
} }
func (functor *baseFunctor) GetParams() (params []ExprFuncParam) {
if functor.info != nil {
return functor.info.Params()
} else {
return []ExprFuncParam{}
}
}
func (functor *baseFunctor) SetFunc(info ExprFunc) { func (functor *baseFunctor) SetFunc(info ExprFunc) {
functor.info = info functor.info = info
} }
@@ -34,7 +42,7 @@ func (functor *baseFunctor) GetFunc() ExprFunc {
return functor.info return functor.info
} }
// ---- Linking with the functions of Go // ---- Linking with Go functions
type golangFunctor struct { type golangFunctor struct {
baseFunctor baseFunctor
f FuncTemplate f FuncTemplate
@@ -48,31 +56,39 @@ func (functor *golangFunctor) Invoke(ctx ExprContext, name string, args []any) (
return functor.f(ctx, name, args) return functor.f(ctx, name, args)
} }
// ---- Linking with the functions of Expr // ---- Linking with Expr functions
type exprFunctor struct { type exprFunctor struct {
baseFunctor baseFunctor
params []string params []ExprFuncParam
expr Expr expr Expr
defCtx ExprContext
} }
func newExprFunctor(e Expr, params []string) *exprFunctor { // func newExprFunctor(e Expr, params []string, ctx ExprContext) *exprFunctor {
return &exprFunctor{expr: e, params: params} // return &exprFunctor{expr: e, params: params, defCtx: ctx}
// }
func newExprFunctor(e Expr, params []ExprFuncParam, ctx ExprContext) *exprFunctor {
return &exprFunctor{expr: e, params: params, defCtx: ctx}
} }
func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) { func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
if functor.defCtx != nil {
ctx.Merge(functor.defCtx)
}
for i, p := range functor.params { for i, p := range functor.params {
if i < len(args) { if i < len(args) {
arg := args[i] arg := args[i]
if funcArg, ok := arg.(Functor); ok { if funcArg, ok := arg.(Functor); ok {
// ctx.RegisterFunc(p, functor, 0, -1) // ctx.RegisterFunc(p, functor, 0, -1)
ctx.RegisterFunc(p, funcArg, typeAny, []ExprFuncParam{ paramSpecs := funcArg.GetParams()
newFuncParam(paramValue), ctx.RegisterFunc(p.Name(), funcArg, typeAny, paramSpecs)
})
} else { } else {
ctx.UnsafeSetVar(p, arg) ctx.UnsafeSetVar(p.Name(), arg)
} }
} else { } else {
ctx.UnsafeSetVar(p, nil) ctx.UnsafeSetVar(p.Name(), nil)
} }
} }
result, err = functor.expr.eval(ctx, false) result, err = functor.expr.eval(ctx, false)
-25
View File
@@ -30,31 +30,6 @@ func registerImport(name string, importFunc func(ExprContext), description strin
moduleRegister[name] = newModule(importFunc, description) moduleRegister[name] = newModule(importFunc, description)
} }
// func ImportInContext(ctx ExprContext, name string) (exists bool) {
// var mod *module
// if mod, exists = moduleRegister[name]; exists {
// mod.importFunc(ctx)
// mod.imported = true
// }
// return
// }
// func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) {
// var matched bool
// for name, mod := range moduleRegister {
// if matched, err = filepath.Match(pattern, name); err == nil {
// if matched {
// count++
// mod.importFunc(ctx)
// mod.imported = true
// }
// } else {
// break
// }
// }
// return
// }
func IterateModules(op func(name, description string, imported bool) bool) { func IterateModules(op func(name, description string, imported bool) bool) {
if op != nil { if op != nil {
for name, mod := range moduleRegister { for name, mod := range moduleRegister {
+38 -35
View File
@@ -22,13 +22,22 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
} }
// -------- eval func call // -------- eval func call
func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) { func checkFunctionCall(ctx ExprContext, name string, varParams *[]any) (err error) {
if info, exists, owner := GetFuncInfo(ctx, name); exists { if info, exists, owner := GetFuncInfo(ctx, name); exists {
if info.MinArgs() > len(params) { passedCount := len(*varParams)
err = errTooFewParams(info.MinArgs(), info.MaxArgs(), len(params)) if info.MinArgs() > passedCount {
err = errTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
} }
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(params) { for i, p := range info.Params() {
err = errTooMuchParams(info.MaxArgs(), len(params)) if i >= passedCount {
if !p.IsOptional() {
break
}
*varParams = append(*varParams, p.DefaultValue())
}
}
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(*varParams) {
err = errTooMuchParams(name, info.MaxArgs(), len(*varParams))
} }
if err == nil && owner != ctx { if err == nil && owner != ctx {
// ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs()) // ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
@@ -44,7 +53,7 @@ func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
ctx := cloneContext(parentCtx) ctx := cloneContext(parentCtx)
name, _ := self.tk.Value.(string) name, _ := self.tk.Value.(string)
// fmt.Printf("Call %s(), context: %p\n", name, ctx) // fmt.Printf("Call %s(), context: %p\n", name, ctx)
params := make([]any, len(self.children)) params := make([]any, len(self.children), len(self.children)+5)
for i, tree := range self.children { for i, tree := range self.children {
var param any var param any
if param, err = tree.compute(ctx); err != nil { if param, err = tree.compute(ctx); err != nil {
@@ -52,8 +61,9 @@ func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
} }
params[i] = param params[i] = param
} }
if err == nil { if err == nil {
if err = checkFunctionCall(ctx, name, params); err == nil { if err = checkFunctionCall(ctx, name, &params); err == nil {
if v, err = ctx.Call(name, params); err == nil { if v, err = ctx.Call(name, params); err == nil {
exportObjects(parentCtx, ctx) exportObjects(parentCtx, ctx)
} }
@@ -75,44 +85,37 @@ func newFuncDefTerm(tk *Token, args []*term) *term {
} }
// -------- eval func def // -------- eval func def
// TODO // func _evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
// type funcDefFunctor struct { // bodySpec := self.value()
// params []string // if expr, ok := bodySpec.(*ast); ok {
// expr Expr // paramList := make([]string, 0, len(self.children))
// } // for _, param := range self.children {
// paramList = append(paramList, param.source())
// func (funcDef *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
// for i, p := range funcDef.params {
// if i < len(args) {
// arg := args[i]
// if functor, ok := arg.(Functor); ok {
// // ctx.RegisterFunc(p, functor, 0, -1)
// ctx.RegisterFunc2(p, functor, typeAny, []ExprFuncParam{
// newFuncParam(paramValue),
// })
// } else {
// ctx.UnsafeSetVar(p, arg)
// }
// } else {
// ctx.UnsafeSetVar(p, nil)
// } // }
// v = newExprFunctor(expr, paramList, ctx)
// } else {
// err = errors.New("invalid function definition: the body specification must be an expression")
// } // }
// result, err = funcDef.expr.eval(ctx, false)
// return // return
// } // }
func evalFuncDef(ctx ExprContext, self *term) (v any, err error) { func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
bodySpec := self.value() bodySpec := self.value()
if expr, ok := bodySpec.(*ast); ok { if expr, ok := bodySpec.(*ast); ok {
paramList := make([]string, 0, len(self.children)) paramList := make([]ExprFuncParam, 0, len(self.children))
for _, param := range self.children { for _, param := range self.children {
paramList = append(paramList, param.source()) var defValue any
flags := paramFlags(0)
if len(param.children) > 0 {
flags |= pfOptional
if defValue, err = param.children[0].compute(ctx); err != nil {
return
}
}
info := newFuncParamFlagDef(param.source(), flags, defValue)
paramList = append(paramList, info)
} }
v = newExprFunctor(expr, paramList) v = newExprFunctor(expr, paramList, ctx)
// v = &funcDefFunctor{
// params: paramList,
// expr: expr,
// }
} else { } else {
err = errors.New("invalid function definition: the body specification must be an expression") err = errors.New("invalid function definition: the body specification must be an expression")
} }
+1 -3
View File
@@ -9,9 +9,7 @@ import "fmt"
// -------- variable term // -------- variable term
func newVarTerm(tk *Token) *term { func newVarTerm(tk *Token) *term {
t := &term{ t := &term{
tk: *tk, tk: *tk,
// class: classVar,
// kind: kindUnknown,
parent: nil, parent: nil,
children: nil, children: nil,
position: posLeaf, position: posLeaf,
+3 -6
View File
@@ -36,12 +36,9 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
// ctx.RegisterFuncInfo(info) // ctx.RegisterFuncInfo(info)
ctx.RegisterFunc(leftTerm.source(), info.Functor(), info.ReturnType(), info.Params()) ctx.RegisterFunc(leftTerm.source(), info.Functor(), info.ReturnType(), info.Params())
} else if funcDef, ok := functor.(*exprFunctor); ok { } else if funcDef, ok := functor.(*exprFunctor); ok {
paramSpecs := ForAll(funcDef.params, newFuncParam) // paramSpecs := ForAll(funcDef.params, newFuncParam)
// paramCount := len(funcDef.params) paramSpecs := ForAll(funcDef.params, func(p ExprFuncParam) ExprFuncParam { return p })
// paramSpecs := make([]ExprFuncParam, paramCount)
// for i := range paramSpecs {
// paramSpecs[i] = newFuncParam(funcDef.params[i])
// }
ctx.RegisterFunc(leftTerm.source(), functor, typeAny, paramSpecs) ctx.RegisterFunc(leftTerm.source(), functor, typeAny, paramSpecs)
} else { } else {
err = self.Errorf("unknown function %s()", funcName) err = self.Errorf("unknown function %s()", funcName)
+27 -74
View File
@@ -23,34 +23,28 @@ func NewParser(ctx ExprContext) (p *parser) {
} }
func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token) (tree *term, err error) { func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token) (tree *term, err error) {
// name, _ := tk.Value.(string)
// funcObj := self.ctx.GetFuncInfo(name)
// if funcObj == nil {
// err = fmt.Errorf("unknown function %s()", name)
// return
// }
// maxArgs := funcObj.MaxArgs()
// if maxArgs < 0 {
// maxArgs = funcObj.MinArgs() + 10
// }
// args := make([]*term, 0, maxArgs)
args := make([]*term, 0, 10) args := make([]*term, 0, 10)
itemExpected := false
lastSym := SymUnknown lastSym := SymUnknown
for lastSym != SymClosedRound && lastSym != SymEos { for lastSym != SymClosedRound && lastSym != SymEos {
var subTree *ast var subTree *ast
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil { if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err != nil {
if subTree.root != nil {
args = append(args, subTree.root)
}
} else {
break break
} }
prev := scanner.Previous()
if subTree.root != nil {
args = append(args, subTree.root)
} else if itemExpected {
err = prev.ErrorExpectedGot("function-param-value")
break
}
itemExpected = prev.Sym == SymComma
lastSym = scanner.Previous().Sym lastSym = scanner.Previous().Sym
} }
if err == nil { if err == nil {
// TODO Check arguments
if lastSym != SymClosedRound { if lastSym != SymClosedRound {
err = errors.New("unterminate arguments list") err = errors.New("unterminated arguments list")
} else { } else {
tree = newFuncCallTerm(tk, args) tree = newFuncCallTerm(tk, args)
} }
@@ -58,62 +52,12 @@ func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token)
return return
} }
// func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
// // Example: "add = func(x,y) {x+y}
// var body *ast
// args := make([]*term, 0)
// lastSym := SymUnknown
// itemExpected := false
// tk := scanner.Previous()
// for lastSym != SymClosedRound && lastSym != SymEos {
// var subTree *ast
// if subTree, err = self.parseItem(scanner, true, SymComma, SymClosedRound); err == nil {
// if subTree.root != nil {
// if subTree.root.symbol() == SymIdentifier {
// args = append(args, subTree.root)
// } else {
// err = tk.ErrorExpectedGotString("param-name", subTree.root.String())
// }
// } else if itemExpected {
// prev := scanner.Previous()
// err = prev.ErrorExpectedGot("function-param")
// break
// }
// } else {
// break
// }
// lastSym = scanner.Previous().Sym
// itemExpected = lastSym == SymComma
// }
// if err == nil && lastSym != SymClosedRound {
// err = tk.ErrorExpectedGot(")")
// }
// if err == nil {
// tk = scanner.Next()
// if tk.Sym == SymOpenBrace {
// body, err = self.parseGeneral(scanner, true, true, SymClosedBrace)
// } else {
// err = tk.ErrorExpectedGot("{")
// }
// }
// if err == nil {
// // TODO Check arguments
// if scanner.Previous().Sym != SymClosedBrace {
// err = scanner.Previous().ErrorExpectedGot("}")
// } else {
// tk = scanner.makeValueToken(SymExpression, "", body)
// tree = newFuncDefTerm(tk, args)
// }
// }
// return
// }
func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) { func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
// Example: "add = func(x,y) {x+y} // Example: "add = func(x,y) {x+y}
var body *ast var body *ast
args := make([]*term, 0) args := make([]*term, 0)
lastSym := SymUnknown lastSym := SymUnknown
defaultParamsStarted := false
itemExpected := false itemExpected := false
tk := scanner.Previous() tk := scanner.Previous()
for lastSym != SymClosedRound && lastSym != SymEos { for lastSym != SymClosedRound && lastSym != SymEos {
@@ -122,9 +66,20 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
param := newTerm(tk) param := newTerm(tk)
args = append(args, param) args = append(args, param)
tk = scanner.Next() tk = scanner.Next()
if tk.Sym == SymEqual {
var paramExpr *ast
defaultParamsStarted = true
if paramExpr, err = self.parseItem(scanner, false, SymComma, SymClosedRound); err != nil {
break
}
param.forceChild(paramExpr.root)
} else if defaultParamsStarted {
err = tk.Errorf("can't mix default and non-default parameters")
break
}
} else if itemExpected { } else if itemExpected {
prev := scanner.Previous() prev := scanner.Previous()
err = prev.ErrorExpectedGot("function-param") err = prev.ErrorExpectedGot("function-param-spec")
break break
} }
lastSym = scanner.Previous().Sym lastSym = scanner.Previous().Sym
@@ -143,7 +98,6 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
} }
} }
if err == nil { if err == nil {
// TODO Check arguments
if scanner.Previous().Sym != SymClosedBrace { if scanner.Previous().Sym != SymClosedBrace {
err = scanner.Previous().ErrorExpectedGot("}") err = scanner.Previous().ErrorExpectedGot("}")
} else { } else {
@@ -225,7 +179,6 @@ func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *t
itemExpected = lastSym == SymComma itemExpected = lastSym == SymComma
} }
if err == nil { if err == nil {
// TODO Check arguments
if lastSym != SymClosedRound { if lastSym != SymClosedRound {
err = scanner.Previous().ErrorExpectedGot(")") err = scanner.Previous().ErrorExpectedGot(")")
} else { } else {
@@ -252,7 +205,6 @@ func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, e
key = tk.Value key = tk.Value
} }
} else { } else {
// err = tk.Errorf("expected dictionary key or closed brace, got %q", tk)
err = tk.ErrorExpectedGot("dictionary-key or }") err = tk.ErrorExpectedGot("dictionary-key or }")
} }
return return
@@ -290,7 +242,6 @@ func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree
itemExpected = lastSym == SymComma itemExpected = lastSym == SymComma
} }
if err == nil { if err == nil {
// TODO Check arguments
if lastSym != SymClosedBrace { if lastSym != SymClosedBrace {
err = scanner.Previous().ErrorExpectedGot("}") err = scanner.Previous().ErrorExpectedGot("}")
} else { } else {
@@ -394,6 +345,8 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
if allowForest { if allowForest {
tree.ToForest() tree.ToForest()
firstToken = true firstToken = true
currentTerm = nil
selectorTerm = nil
continue continue
} else { } else {
err = tk.Errorf(`unexpected token %q, expected ",", "]", or ")"`, tk.source) err = tk.Errorf(`unexpected token %q, expected ",", "]", or ")"`, tk.source)
+18 -7
View File
@@ -12,29 +12,40 @@ import (
type SimpleStore struct { type SimpleStore struct {
varStore map[string]any varStore map[string]any
funcStore map[string]*funcInfo funcStore map[string]ExprFunc
} }
func NewSimpleStore() *SimpleStore { func NewSimpleStore() *SimpleStore {
ctx := &SimpleStore{ ctx := &SimpleStore{
varStore: make(map[string]any), varStore: make(map[string]any),
funcStore: make(map[string]*funcInfo), funcStore: make(map[string]ExprFunc),
} }
//ImportBuiltinsFuncs(ctx)
return ctx return ctx
} }
func filterRefName(name string) bool { return name[0] != '@' }
func filterPrivName(name string) bool { return name[0] != '_' }
func (ctx *SimpleStore) Clone() ExprContext { func (ctx *SimpleStore) Clone() ExprContext {
return &SimpleStore{ return &SimpleStore{
varStore: CloneFilteredMap(ctx.varStore, func(name string) bool { return name[0] != '@' }), varStore: CloneFilteredMap(ctx.varStore, filterRefName),
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }), funcStore: CloneFilteredMap(ctx.funcStore, filterRefName),
}
}
func (ctx *SimpleStore) Merge(src ExprContext) {
for _, name := range src.EnumVars(filterRefName) {
ctx.varStore[name], _ = src.GetVar(name)
}
for _, name := range src.EnumFuncs(filterRefName) {
ctx.funcStore[name], _ = src.GetFuncInfo(name)
} }
} }
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) { func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("vars: {\n") sb.WriteString("vars: {\n")
first := true first := true
for _, name := range ctx.EnumVars(func(name string) bool { return name[0] != '_' }) { for _, name := range ctx.EnumVars(filterPrivName) {
if first { if first {
first = false first = false
} else { } else {
@@ -164,7 +175,7 @@ func (ctx *SimpleStore) EnumFuncs(acceptor func(name string) (accept bool)) (fun
func (ctx *SimpleStore) Call(name string, args []any) (result any, err error) { func (ctx *SimpleStore) Call(name string, args []any) (result any, err error) {
if info, exists := ctx.funcStore[name]; exists { if info, exists := ctx.funcStore[name]; exists {
functor := info.functor functor := info.Functor()
result, err = functor.Invoke(ctx, name, args) result, err = functor.Invoke(ctx, name, args)
} else { } else {
err = fmt.Errorf("unknown function %s()", name) err = fmt.Errorf("unknown function %s()", name)
+5 -50
View File
@@ -5,21 +5,16 @@
package expr package expr
import ( import (
"strings"
"testing" "testing"
) )
func TestExpr(t *testing.T) { func TestExpr(t *testing.T) {
type inputType struct { section := "Expr"
source string
wantResult any
wantErr error
}
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`0?{}`, nil, nil}, /* 1 */ {`0?{}`, nil, nil},
/* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil}, /* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
/* 3 */ {`f=openFile("test-file.txt"); line=readFile(f); closeFile(f); line`, "uno", nil}, /* 3 */ {`builtin "os.file"; f=openFile("test-file.txt"); line=readFile(f); closeFile(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 */ {`
@@ -35,48 +30,8 @@ func TestExpr(t *testing.T) {
it++ it++
`, int64(1), nil}, `, int64(1), nil},
} }
// t.Setenv("EXPR_PATH", ".")
succeeded := 0 // parserTestSpec(t, section, inputs, 3)
failed := 0 parserTest(t, section, inputs)
for i, input := range inputs {
var expr Expr
var gotResult any
var gotErr error
ctx := NewSimpleStore()
// ImportMathFuncs(ctx)
// ImportImportFunc(ctx)
ImportOsFuncs(ctx)
parser := NewParser(ctx)
logTest(t, i+1, "Expr", input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good := true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
if gotResult != input.wantResult {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
good = false
}
}
if good {
succeeded++
} else {
failed++
}
}
t.Logf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed)
} }
+66
View File
@@ -0,0 +1,66 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_fractions_test.go
package expr
import (
"errors"
"testing"
)
func TestFractionsParser(t *testing.T) {
section := "Fraction"
inputs := []inputType{
/* 1 */ {`1|2`, newFraction(1, 2), nil},
/* 2 */ {`1|2 + 1`, newFraction(3, 2), nil},
/* 3 */ {`1|2 - 1`, newFraction(-1, 2), nil},
/* 4 */ {`1|2 * 1`, newFraction(1, 2), nil},
/* 5 */ {`1|2 * 2|3`, newFraction(2, 6), nil},
/* 6 */ {`1|2 / 2|3`, newFraction(3, 4), nil},
/* 7 */ {`1|"5"`, nil, errors.New(`denominator must be integer, got string (5)`)},
/* 8 */ {`"1"|5`, nil, errors.New(`numerator must be integer, got string (1)`)},
/* 9 */ {`1|+5`, nil, errors.New(`[1:3] infix operator "|" requires two non-nil operands, got 1`)},
/* 10 */ {`1|(-2)`, newFraction(-1, 2), nil},
/* 11 */ {`builtin "math.arith"; add(1|2, 2|3)`, newFraction(7, 6), nil},
/* 12 */ {`builtin "math.arith"; add(1|2, 1.0, 2)`, float64(3.5), nil},
/* 13 */ {`builtin "math.arith"; mul(1|2, 2|3)`, newFraction(2, 6), nil},
/* 14 */ {`builtin "math.arith"; mul(1|2, 1.0, 2)`, float64(1.0), nil},
/* 15 */ {`1|0`, nil, errors.New(`division by zero`)},
/* 16 */ {`fract(-0.5)`, newFraction(-1, 2), nil},
/* 17 */ {`fract("")`, (*FractionType)(nil), errors.New(`bad syntax`)},
/* 18 */ {`fract("-1")`, newFraction(-1, 1), nil},
/* 19 */ {`fract("+1")`, newFraction(1, 1), nil},
/* 20 */ {`fract("1a")`, (*FractionType)(nil), errors.New(`strconv.ParseInt: parsing "1a": invalid syntax`)},
/* 21 */ {`fract(1,0)`, nil, errors.New(`fract(): division by zero`)},
}
parserTest(t, section, inputs)
}
func TestFractionToStringSimple(t *testing.T) {
source := newFraction(1, 2)
want := "1|2"
got := source.ToString(0)
if got != want {
t.Errorf(`(1,2) -> result = %v [%T], want = %v [%T]`, got, got, want, want)
}
}
func TestFractionToStringMultiline(t *testing.T) {
source := newFraction(1, 2)
want := "1\n-\n2"
got := source.ToString(MultiLine)
if got != want {
t.Errorf(`(1,2) -> result = %v [%T], want = %v [%T]`, got, got, want, want)
}
}
// TODO Check this test: the output string ends with a space
func _TestToStringMultilineTty(t *testing.T) {
source := newFraction(-1, 2)
want := "\x1b[4m-1\x1b[0m\n2"
got := source.ToString(MultiLine | TTY)
if got != want {
t.Errorf(`(1,2) -> result = %#v [%T], want = %#v [%T]`, got, got, want, want)
}
}
+67
View File
@@ -0,0 +1,67 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_func-base_test.go
package expr
import (
"errors"
"testing"
)
func TestFuncBase(t *testing.T) {
section := "Func-Base"
inputs := []inputType{
/* 1 */ {`isNil(nil)`, true, nil},
/* 2 */ {`v=nil; isNil(v)`, true, nil},
/* 3 */ {`v=5; isNil(v)`, false, nil},
/* 4 */ {`int(true)`, int64(1), nil},
/* 5 */ {`int(false)`, int64(0), nil},
/* 6 */ {`int(3.1)`, int64(3), nil},
/* 7 */ {`int(3.9)`, int64(3), nil},
/* 8 */ {`int("432")`, int64(432), nil},
/* 9 */ {`int("1.5")`, nil, errors.New(`strconv.Atoi: parsing "1.5": invalid syntax`)},
/* 10 */ {`int("432", 4)`, nil, errors.New(`int(): too much params -- expected 1, got 2`)},
/* 11 */ {`int(nil)`, nil, errors.New(`int(): can't convert <nil> to int`)},
/* 12 */ {`isInt(2+1)`, true, nil},
/* 13 */ {`isInt(3.1)`, false, nil},
/* 14 */ {`isFloat(3.1)`, true, nil},
/* 15 */ {`isString("3.1")`, true, nil},
/* 16 */ {`isString("3" + 1)`, true, nil},
/* 17 */ {`isList(["3", 1])`, true, nil},
/* 18 */ {`isDict({"a":"3", "b":1})`, true, nil},
/* 19 */ {`isFract(1|3)`, true, nil},
/* 20 */ {`isFract(3|1)`, false, nil},
/* 21 */ {`isRational(3|1)`, true, nil},
/* 22 */ {`fract("2.2(3)")`, newFraction(67, 30), nil},
/* 23 */ {`fract("1.21(3)")`, newFraction(91, 75), nil},
/* 24 */ {`fract(1.21(3))`, newFraction(91, 75), nil},
/* 25 */ {`fract(1.21)`, newFraction(121, 100), nil},
/* 26 */ {`dec(2)`, float64(2), nil},
/* 27 */ {`dec(2.0)`, float64(2), nil},
/* 28 */ {`dec("2.0")`, float64(2), nil},
/* 29 */ {`dec(true)`, float64(1), nil},
/* 30 */ {`dec(true")`, nil, errors.New("[1:11] missing string termination \"")},
/* 31 */ {`dec()`, nil, errors.New(`dec(): too few params -- expected 1, got 0`)},
/* 32 */ {`dec(1,2,3)`, nil, errors.New(`dec(): too much params -- expected 1, got 3`)},
/* 33 */ {`isBool(false)`, true, nil},
/* 34 */ {`fract(1|2)`, newFraction(1, 2), nil},
/* 35 */ {`fract(12,2)`, newFraction(6, 1), nil},
/* 36 */ {`bool(2)`, true, nil},
/* 37 */ {`bool(1|2)`, true, nil},
/* 38 */ {`bool(1.0)`, true, nil},
/* 39 */ {`bool("1")`, true, nil},
/* 40 */ {`bool(false)`, false, nil},
/* 41 */ {`bool([1])`, nil, errors.New(`bool(): can't convert list to bool`)},
/* 42 */ {`dec(false)`, float64(0), nil},
/* 43 */ {`dec(1|2)`, float64(0.5), nil},
/* 44 */ {`dec([1])`, nil, errors.New(`dec(): can't convert list to float`)},
// /* 45 */ {`string([1])`, nil, errors.New(`string(): can't convert list to string`)},
}
t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 2)
parserTest(t, section, inputs)
}
+24
View File
@@ -0,0 +1,24 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_func-import_test.go
package expr
import (
"testing"
)
func TestFuncImport(t *testing.T) {
section := "Func-Import"
inputs := []inputType{
/* 1 */ {`builtin "import"; import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 2 */ {`builtin "import"; import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 3 */ {`builtin "import"; importAll("./test-funcs.expr"); six()`, int64(6), nil},
/* 4 */ {`builtin "import"; import("./sample-export-all.expr"); six()`, int64(6), nil},
}
t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 69)
parserTest(t, section, inputs)
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_func-math-arith_test.go
package expr
import (
"errors"
"testing"
)
func TestFuncMathArith(t *testing.T) {
section := "Func-Math-Arith"
inputs := []inputType{
/* 1 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil},
/* 2 */ {`builtin "math.arith"; add(1,2,3)`, int64(6), nil},
/* 3 */ {`builtin "math.arith"; mulX(1,2,3)`, nil, errors.New(`unknown function mulX()`)},
/* 4 */ {`builtin "math.arith"; add(1+4,3+2,5*(3-2))`, int64(15), nil},
/* 5 */ {`builtin "math.arith"; add(add(1+4),3+2,5*(3-2))`, int64(15), 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},
/* 8 */ {`builtin "math.arith"; var2="abc"; add(1,2) but var2`, "abc", nil},
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 69)
parserTest(t, section, inputs)
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_func-os_test.go
package expr
import (
"errors"
"testing"
)
func TestFuncOs(t *testing.T) {
section := "Func-OS"
inputs := []inputType{
/* 1 */ {`builtin "os.file"`, int64(1), nil},
/* 2 */ {`builtin "os.file"; handle=openFile("/etc/hosts"); closeFile(handle)`, true, nil},
/* 3 */ {`builtin "os.file"; handle=openFile("/etc/hostsX")`, nil, errors.New(`open /etc/hostsX: no such file or directory`)},
/* 4 */ {`builtin "os.file"; handle=createFile("/tmp/dummy"); closeFile(handle)`, true, nil},
/* 5 */ {`builtin "os.file"; handle=appendFile("/tmp/dummy"); writeFile(handle, "bye-bye"); closeFile(handle)`, true, nil},
/* 6 */ {`builtin "os.file"; handle=openFile("/tmp/dummy"); word=readFile(handle, "-"); closeFile(handle);word`, "bye", nil},
/* 7 */ {`builtin "os.file"; word=readFile(nil, "-")`, nil, errors.New(`readFileFunc(): invalid file handle`)},
/* 7 */ {`builtin "os.file"; writeFile(nil, "bye")`, nil, errors.New(`writeFileFunc(): invalid file handle`)},
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 69)
parserTest(t, section, inputs)
}
+69
View File
@@ -0,0 +1,69 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_func-string_test.go
package expr
import (
"errors"
"testing"
)
func TestFuncString(t *testing.T) {
section := "Func-String"
inputs := []inputType{
/* 1 */ {`builtin "string"; joinStr("-", "one", "two", "three")`, "one-two-three", nil},
/* 2 */ {`builtin "string"; joinStr("-", ["one", "two", "three"])`, "one-two-three", nil},
/* 3 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr("-", ls)`, "one-two-three", nil},
/* 4 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr(1, ls)`, nil, errors.New(`joinStr() the "separator" parameter must be a string, got a int64 (1)`)},
/* 5 */ {`builtin "string"; ls= ["one", 2, "three"]; joinStr("-", ls)`, nil, errors.New(`joinStr() expected string, got int64 (2)`)},
/* 6 */ {`builtin "string"; "<"+trimStr(" bye bye ")+">"`, "<bye bye>", nil},
/* 7 */ {`builtin "string"; subStr("0123456789", 1,2)`, "12", nil},
/* 8 */ {`builtin "string"; subStr("0123456789", -3,2)`, "78", nil},
/* 9 */ {`builtin "string"; subStr("0123456789", -3)`, "789", nil},
/* 10 */ {`builtin "string"; subStr("0123456789")`, "0123456789", nil},
/* 11 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "012")`, true, nil},
/* 12 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "0125")`, false, nil},
/* 13 */ {`builtin "string"; startsWithStr("0123456789")`, nil, errors.New(`startsWithStr(): too few params -- expected 2 or more, got 1`)},
/* 14 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "789")`, true, nil},
/* 15 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "0125")`, false, nil},
/* 16 */ {`builtin "string"; endsWithStr("0123456789")`, nil, errors.New(`endsWithStr(): too few params -- expected 2 or more, got 1`)},
/* 17 */ {`builtin "string"; splitStr("one-two-three", "-")`, newListA("one", "two", "three"), nil},
/* 18 */ {`builtin "string"; joinStr("-", [1, "two", "three"])`, nil, errors.New(`joinStr() expected string, got int64 (1)`)},
/* 19 */ {`builtin "string"; joinStr()`, nil, errors.New(`joinStr(): too few params -- expected 1 or more, got 0`)},
/* 69 */ /*{`builtin "string"; $$global`, `vars: {
}
funcs: {
add(any=0 ...) -> number,
dec(any) -> decimal,
endsWithStr(source, suffix) -> boolean,
fract(any, denominator=1) -> fraction,
import( ...) -> any,
importAll( ...) -> any,
int(any) -> integer,
isBool(any) -> boolean,
isDec(any) -> boolean,
isDict(any) -> boolean,
isFloat(any) -> boolean,
isFract(any) -> boolean,
isInt(any) -> boolean,
isList(any) -> boolean,
isNil(any) -> boolean,
isString(any) -> boolean,
joinStr(separator, item="" ...) -> string,
mul(any=1 ...) -> number,
splitStr(source, separator="", count=-1) -> list of string,
startsWithStr(source, prefix) -> boolean,
subStr(source, start=0, count=-1) -> string,
trimStr(source) -> string
}
`, nil},*/
}
//t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, "Func", inputs, 69)
parserTest(t, section, inputs)
}
+43 -99
View File
@@ -10,107 +10,51 @@ import (
) )
func TestFuncs(t *testing.T) { func TestFuncs(t *testing.T) {
section := "Funcs"
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`isNil(nil)`, true, nil}, /* 1 */ {`two=func(){2}; two()`, int64(2), nil},
/* 2 */ {`v=nil; isNil(v)`, true, nil}, /* 2 */ {`double=func(x) {2*x}; (double(3))`, int64(6), nil},
/* 3 */ {`v=5; isNil(v)`, false, nil}, /* 3 */ {`double=func(x){2*x}; double(3)`, int64(6), nil},
/* 4 */ {`int(true)`, int64(1), nil}, /* 4 */ {`double=func(x){2*x}; a=5; double(3+a) + 1`, int64(17), nil},
/* 5 */ {`int(false)`, int64(0), nil}, /* 5 */ {`double=func(x){2*x}; a=5; two=func() {2}; (double(3+a) + 1) * two()`, int64(34), nil},
/* 6 */ {`int(3.1)`, int64(3), nil}, /* 6 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
/* 7 */ {`int(3.9)`, int64(3), nil}, /* 7 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
/* 8 */ {`int("432")`, int64(432), nil}, /* 8 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
/* 9 */ {`int("1.5")`, nil, errors.New(`strconv.Atoi: parsing "1.5": invalid syntax`)}, /* 9 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable or function "x"`)},
/* 10 */ {`int("432", 4)`, nil, errors.New(`too much params -- expected 1, got 2`)}, /* 10 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 11 */ {`int(nil)`, nil, errors.New(`int() can't convert <nil> to int`)}, /* 11 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 12 */ {`two=func(){2}; two()`, int64(2), nil}, /* 12 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
/* 13 */ {`double=func(x) {2*x}; (double(3))`, int64(6), nil}, /* 13 */ {`two=func(){2}; four=func(f){f()+f()}; four(two)`, int64(4), nil},
/* 14 */ {`double=func(x){2*x}; double(3)`, int64(6), nil}, /* 14 */ {`two=func(){2}; two(123)`, nil, errors.New(`two(): too much params -- expected 0, got 1`)},
/* 15 */ {`double=func(x){2*x}; a=5; double(3+a) + 1`, int64(17), nil}, /* 15 */ {`f=func(x,n=2){x+n}; f(3)`, int64(5), nil},
/* 16 */ {`double=func(x){2*x}; a=5; two=func() {2}; (double(3+a) + 1) * two()`, int64(34), nil}, /* 16 */ {`f=func(x,n=2,y){x+n}`, nil, errors.New(`[1:16] can't mix default and non-default parameters`)},
/* 17 */ {`builtin "import"; import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil}, /* 17 */ {`f=func(x,n){1}; f(3,4,)`, nil, errors.New(`[1:24] expected "function-param-value", got ")"`)},
/* 18 */ {`builtin "import"; import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 19 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
/* 20 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
/* 21 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
/* 22 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable or function "x"`)},
/* 23 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 24 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 25 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
/* 26 */ {`builtin "import"; importAll("./test-funcs.expr"); six()`, int64(6), nil},
/* 27 */ {`builtin "import"; import("./sample-export-all.expr"); six()`, int64(6), nil},
/* 28 */ {`builtin "string"; joinStr("-", "one", "two", "three")`, "one-two-three", nil},
/* 29 */ {`builtin "string"; joinStr("-", ["one", "two", "three"])`, "one-two-three", nil},
/* 30 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr("-", ls)`, "one-two-three", nil},
/* 31 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr(1, ls)`, nil, errors.New(`joinStr() the "separator" parameter must be a string, got a int64 (1)`)},
/* 32 */ {`builtin "string"; ls= ["one", 2, "three"]; joinStr("-", ls)`, nil, errors.New(`joinStr() expected string, got int64 (2)`)},
/* 33 */ {`builtin "string"; "<"+trimStr(" bye bye ")+">"`, "<bye bye>", nil},
/* 34 */ {`builtin "string"; subStr("0123456789", 1,2)`, "12", nil},
/* 35 */ {`builtin "string"; subStr("0123456789", -3,2)`, "78", nil},
/* 36 */ {`builtin "string"; subStr("0123456789", -3)`, "789", nil},
/* 37 */ {`builtin "string"; subStr("0123456789")`, "0123456789", nil},
/* 38 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "012")`, true, nil},
/* 39 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "0125")`, false, nil},
/* 40 */ {`builtin "string"; startsWithStr("0123456789")`, nil, errors.New(`too few params -- expected 2 or more, got 1`)},
/* 41 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "789")`, true, nil},
/* 42 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "0125")`, false, nil},
/* 43 */ {`builtin "string"; endsWithStr("0123456789")`, nil, errors.New(`too few params -- expected 2 or more, got 1`)},
/* 44 */ {`builtin "string"; splitStr("one-two-three", "-", )`, newListA("one", "two", "three"), nil},
/* 45 */ {`isInt(2+1)`, true, nil},
/* 46 */ {`isInt(3.1)`, false, nil},
/* 47 */ {`isFloat(3.1)`, true, nil},
/* 48 */ {`isString("3.1")`, true, nil},
/* 49 */ {`isString("3" + 1)`, true, nil},
/* 50 */ {`isList(["3", 1])`, true, nil},
/* 51 */ {`isDict({"a":"3", "b":1})`, true, nil},
/* 52 */ {`isFract(1|3)`, true, nil},
/* 53 */ {`isFract(3|1)`, false, nil},
/* 54 */ {`isRational(3|1)`, true, nil},
/* 55 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil},
/* 56 */ {`fract("2.2(3)")`, newFraction(67, 30), nil},
/* 57 */ {`fract("1.21(3)")`, newFraction(91, 75), nil},
/* 58 */ {`fract(1.21(3))`, newFraction(91, 75), nil},
/* 59 */ {`fract(1.21)`, newFraction(121, 100), nil},
/* 60 */ {`dec(2)`, float64(2), nil},
/* 61 */ {`dec(2.0)`, float64(2), nil},
/* 62 */ {`dec("2.0")`, float64(2), nil},
/* 63 */ {`dec(true)`, float64(1), nil},
/* 64 */ {`dec(true")`, nil, errors.New("[1:11] missing string termination \"")},
/* 65 */ {`builtin "string"; joinStr("-", [1, "two", "three"])`, nil, errors.New(`joinStr() expected string, got int64 (1)`)},
/* 66 */ {`dec()`, nil, errors.New(`too few params -- expected 1, got 0`)},
/* 67 */ {`dec(1,2,3)`, nil, errors.New(`too much params -- expected 1, got 3`)},
/* 68 */ {`builtin "string"; joinStr()`, nil, errors.New(`too few params -- expected 1 or more, got 0`)},
/* 69 */ /*{`builtin "string"; $$global`, `vars: {
}
funcs: {
add(any=0 ...) -> number,
dec(any) -> decimal,
endsWithStr(source, suffix) -> boolean,
fract(any, denominator=1) -> fraction,
import( ...) -> any,
importAll( ...) -> any,
int(any) -> integer,
isBool(any) -> boolean,
isDec(any) -> boolean,
isDict(any) -> boolean,
isFloat(any) -> boolean,
isFract(any) -> boolean,
isInt(any) -> boolean,
isList(any) -> boolean,
isNil(any) -> boolean,
isString(any) -> boolean,
joinStr(separator, item="" ...) -> string,
mul(any=1 ...) -> number,
splitStr(source, separator="", count=-1) -> list of string,
startsWithStr(source, prefix) -> boolean,
subStr(source, start=0, count=-1) -> string,
trimStr(source) -> string
}
`, nil},*/
} }
t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, "Func", inputs, 69) // parserTestSpec(t, section, inputs, 17)
parserTest(t, "Func", inputs) parserTest(t, section, inputs)
}
func dummy(ctx ExprContext, name string, args []any) (result any, err error) {
return
}
func TestFunctionToStringSimple(t *testing.T) {
source := newGolangFunctor(dummy)
want := "func() {<body>}"
got := source.ToString(0)
if got != want {
t.Errorf(`(func() -> result = %v [%T], want = %v [%T]`, got, got, want, want)
}
}
func TestFunctionGetFunc(t *testing.T) {
source := newGolangFunctor(dummy)
want := ExprFunc(nil)
got := source.GetFunc()
if got != want {
t.Errorf(`(func() -> result = %v [%T], want = %v [%T]`, got, got, want, want)
}
} }
+6
View File
@@ -46,6 +46,12 @@ func TestListParser(t *testing.T) {
/* 24 */ {`["a","b","c","d"][1]`, "b", nil}, /* 24 */ {`["a","b","c","d"][1]`, "b", nil},
/* 25 */ {`["a","b","c","d"][1,1]`, nil, errors.New(`[1:19] one index only is allowed`)}, /* 25 */ {`["a","b","c","d"][1,1]`, nil, errors.New(`[1:19] one index only is allowed`)},
/* 26 */ {`[0,1,2,3,4][:]`, ListType{int64(0), int64(1), int64(2), int64(3), int64(4)}, nil}, /* 26 */ {`[0,1,2,3,4][:]`, ListType{int64(0), int64(1), int64(2), int64(3), int64(4)}, nil},
/* 27 */ {`["a", "b", "c"] << ;`, nil, errors.New(`[1:18] infix operator "<<" requires two non-nil operands, got 1`)},
/* 28 */ {`2 << 3;`, nil, errors.New(`[1:4] left operand '2' [integer] and right operand '3' [integer] are not compatible with operator "<<"`)},
/* 29 */ {`but >> ["a", "b", "c"]`, nil, errors.New(`[1:6] infix operator ">>" requires two non-nil operands, got 0`)},
/* 30 */ {`2 >> 3;`, nil, errors.New(`[1:4] left operand '2' [integer] and right operand '3' [integer] are not compatible with operator ">>"`)},
/* 31 */ {`a=[1,2]; a<<3`, []any{1, 2, 3}, nil},
/* 33 */ {`a=[1,2]; 5>>a`, []any{5, 1, 2}, nil},
// /* 8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, nil}, // /* 8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, nil},
// /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil}, // /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil},
+31
View File
@@ -0,0 +1,31 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_module-register_test.go
package expr
import (
"testing"
)
func TestIterateModules(t *testing.T) {
section := "Module-Register"
mods := make([]string, 0, 100)
IterateModules(func(name, description string, imported bool) bool {
mods = append(mods, name)
return true
})
t.Logf("%s -- IterateModules(): %v", section, mods)
if len(mods) == 0 {
t.Errorf("Module-List: got-length zero, expected-length greater than zero")
}
// IterateModules(func(name, description string, imported bool) bool {
// return false
// })
// if len(mods) > 0 {
// t.Errorf("Module-List: got-length greater than zero, expected-length zero")
// }
}
+4 -22
View File
@@ -138,28 +138,10 @@ func TestGeneralParser(t *testing.T) {
/* 117 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)}, /* 117 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)},
/* 118 */ {`{"key":}`, nil, errors.New(`[1:9] expected "dictionary-value", got "}"`)}, /* 118 */ {`{"key":}`, nil, errors.New(`[1:9] expected "dictionary-value", got "}"`)},
/* 119 */ {`{}`, &DictType{}, nil}, /* 119 */ {`{}`, &DictType{}, nil},
/* 120 */ {`1|2`, newFraction(1, 2), nil}, /* 120 */ {`v=10; v++; v`, int64(11), nil},
/* 121 */ {`1|2 + 1`, newFraction(3, 2), nil}, /* 121 */ {`1+1|2+0.5`, float64(2), nil},
/* 122 */ {`1|2 - 1`, newFraction(-1, 2), nil}, /* 122 */ {`1.2()`, newFraction(6, 5), nil},
/* 123 */ {`1|2 * 1`, newFraction(1, 2), nil}, /* 123 */ {`1|(2-2)`, nil, errors.New(`division by zero`)},
/* 124 */ {`1|2 * 2|3`, newFraction(2, 6), nil},
/* 125 */ {`1|2 / 2|3`, newFraction(3, 4), nil},
/* 126 */ {`builtin "math.arith"; add(1,2,3)`, int64(6), nil},
/* 127 */ {`builtin "math.arith"; mulX(1,2,3)`, nil, errors.New(`unknown function mulX()`)},
/* 128 */ {`builtin "math.arith"; add(1+4,3+2,5*(3-2))`, int64(15), nil},
/* 129 */ {`builtin "math.arith"; add(add(1+4),3+2,5*(3-2))`, int64(15), nil},
/* 130 */ {`builtin "math.arith"; add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil},
/* 131 */ {`builtin "math.arith"; a=5; b=2; add(a, b*3)`, int64(11), nil},
/* 132 */ {`builtin "math.arith"; var2="abc"; add(1,2) but var2`, "abc", nil},
/* 133 */ {`builtin "math.arith"; add(1|2, 2|3)`, newFraction(7, 6), nil},
/* 134 */ {`builtin "math.arith"; add(1|2, 1.0, 2)`, float64(3.5), nil},
/* 135 */ {`builtin "math.arith"; mul(1|2, 2|3)`, newFraction(2, 6), nil},
/* 136 */ {`builtin "math.arith"; mul(1|2, 1.0, 2)`, float64(1.0), nil},
/* 137 */ {`builtin "os.file"`, int64(1), nil},
/* 138 */ {`v=10; v++; v`, int64(11), nil},
/* 139 */ {`1+1|2+0.5`, float64(2), nil},
/* 140 */ {`1.2()`, newFraction(6, 5), nil},
/* 141 */ {`1|(2-2)`, nil, errors.New(`division by zero`)},
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
+17
View File
@@ -54,6 +54,14 @@ func TestIsString(t *testing.T) {
succeeded++ succeeded++
} }
count++
if isIterator("fake") {
t.Errorf(`%d: isIterator("fake") -> result = true, want false`, count)
failed++
} else {
succeeded++
}
count++ count++
b, ok := toBool(true) b, ok := toBool(true)
if !(ok && b) { if !(ok && b) {
@@ -72,6 +80,15 @@ func TestIsString(t *testing.T) {
succeeded++ succeeded++
} }
count++
b, ok = toBool([]int{})
if ok {
t.Errorf("%d: toBool([]) b=%v, ok=%v -> result = true, want false", count, b, ok)
failed++
} else {
succeeded++
}
t.Logf("test count: %d, succeeded count: %d, failed count: %d", count, succeeded, failed) t.Logf("test count: %d, succeeded count: %d, failed count: %d", count, succeeded, failed)
} }
+9
View File
@@ -229,3 +229,12 @@ func (self *term) evalPrefix(ctx ExprContext) (childValue any, err error) {
} }
return return
} }
// NOTE Temporary solution to support function parameters with default value
func (self *term) forceChild(c *term) {
if self.children == nil {
self.children = make([]*term, 0, 1)
}
self.children = append(self.children, c)
}