Compare commits

..

58 Commits

Author SHA1 Message Date
camoroso 9fb611aa20 Formatter option is now composed of two data: flags (lower 16 bits) and indentation size (higher 16 bits).
DictType and ListType formatter use both indent and flag options.
Simple-store now makes a DictType from its data and its ToString() function returns dict.ToString()
2024-06-11 16:32:01 +02:00
camoroso 56d6d06d15 simple-store.go: newline removed after context last brace in ToString() 2024-06-10 20:58:38 +02:00
camoroso d9f7e5b1ad common-errors.go: exported all error functions.
builtin-string.go: renamed all functions from somthingStr() to strSomething()
2024-06-10 20:37:58 +02:00
camoroso 0f54e01ef3 t_scanner_test.go: Test nr 25 changed because now single-quotes can enclose strings 2024-06-10 20:36:03 +02:00
camoroso 63f5db00b3 all test file on builtin functions have been renamed from t_func-*_test.go to t_builtin-*_test.go 2024-06-10 20:34:11 +02:00
camoroso 9745a5d909 utils.go: toInt() -> ToInt(); toBool() -> ToBool() 2024-06-10 19:03:39 +02:00
camoroso 0bb4c96481 scanner.go: Strings can be enclosed between two single-quotes too 2024-06-10 18:52:13 +02:00
camoroso 1757298eb4 formatter.go: typeName() renamed as TypeName() 2024-06-10 09:37:27 +02:00
camoroso 54041552d4 dict-type.go: added MakeDict() constructor 2024-06-10 09:35:48 +02:00
camoroso 5302907dcf external plugins can now request for dependencies 2024-06-09 17:12:57 +02:00
camoroso eb4b17f078 moved all test expression files in the test-resources forlder 2024-06-09 16:02:07 +02:00
camoroso 33d70d6d1a splitted go and expr function bindings in dedicated source files 2024-06-09 10:41:06 +02:00
camoroso 9df9ad5dd1 func-*.go modules renamed as builtin-*.go.
Also changed and exported some identiefier relatet to the builtin feature
2024-06-09 10:28:51 +02:00
camoroso 34dc828ead exported some identifier 2024-06-09 10:13:37 +02:00
camoroso 29bc2c62a3 first plugin support.
Module organization requires a better structure to decouple definitions and implementations
2024-06-09 07:41:56 +02:00
camoroso 8eb2d77ea3 improved position of some common functions 2024-06-09 07:38:29 +02:00
camoroso 53bcf90d2a operator-builtin.go: some error messages improved 2024-06-09 07:36:12 +02:00
camoroso f347b15146 Control vars are now stored in the globalCtx only.
However, it is still allowed to store control var in a local context in special situation
2024-06-08 05:49:28 +02:00
camoroso 115ce26ce9 IsOptional() function of ExprFuncParam renamed as IdDefault(). A new IsOptional() function created to check if a param is optional without requiring a default value 2024-06-07 09:45:02 +02:00
camoroso 227944b3fb Removed eval() function from ast interface and implementation. Check on preset control variables is now done in initDefaultVars() 2024-06-07 09:41:10 +02:00
camoroso 0d01afcc9f member ctx removed from struct parser because it is unused 2024-06-07 09:03:42 +02:00
camoroso 00c76b41f1 list-type.go: two special constructors, MakeList() and ListFromStrigns(), added 2024-06-07 09:02:14 +02:00
camoroso 08e0979cdd import-utils.go: addPresetDirs() replaced by addSearchDirs() 2024-06-07 09:01:18 +02:00
camoroso f04f5822ec common-type-names.go: added type name list-of 2024-06-07 08:59:04 +02:00
camoroso 80d47879e9 t_list_test.go: removed commented code 2024-06-07 08:54:59 +02:00
camoroso 985eb3d19d Merge branch 'main' into feat_plugin 2024-06-06 05:33:56 +02:00
camoroso 45734ab393 new test file t_iter-list.go 2024-06-06 05:33:35 +02:00
camoroso c100cf349d some identier exported; new file import-utils.go 2024-06-06 05:31:35 +02:00
camoroso 8144122d2c fixed some defects in the iter-list implementation 2024-06-05 08:06:39 +02:00
camoroso 188ea354ee it-range.go renamed as it-range.go.unused 2024-06-05 08:05:42 +02:00
camoroso 2fc6bbfe10 removed old simple vars and funcs context implementations 2024-06-05 05:54:12 +02:00
camoroso 847d85605e removed unused commented code 2024-06-05 05:53:02 +02:00
camoroso 9c29392389 changes to adapt errors using typeName() functions 2024-06-05 05:52:27 +02:00
camoroso a7b6e6f8d2 t_list_test.go: some tests added 2024-06-05 05:50:37 +02:00
camoroso a16ac70e4a t_dict_test.go: some tests added 2024-06-05 05:49:07 +02:00
camoroso ab2e3f0528 use of typeName() in error messages 2024-06-05 05:48:02 +02:00
camoroso 974835a8ef utils.go/typeName() and formatter.go/getTipeName() have been merged in formatter.go/typeName() 2024-06-05 05:42:37 +02:00
camoroso 457a656073 tests on collection's item assignments and some other changes 2024-06-05 05:09:13 +02:00
camoroso 9e63e1402e t_parser_test.go: Expr's type names 2024-06-05 05:06:43 +02:00
camoroso e4ded4f746 operator-assign.go: some errors message changed to report the Expr's type names, not the Go ones 2024-06-05 05:05:40 +02:00
camoroso 4e3af837e6 list-type.go: constructor newListA(), called without arguments, now creates an empty list, not a nil object 2024-06-05 05:03:37 +02:00
camoroso ca12722c93 utils.go: new function typeName() 2024-06-05 05:01:34 +02:00
camoroso d96123ab02 The assign operator '=' can now set items in ListType and DictType 2024-06-04 11:07:35 +02:00
camoroso f2d6d63017 fixed an error message (test nr 97) 2024-06-04 11:04:59 +02:00
camoroso 905b2af7fa setItem() function 2024-06-04 11:04:00 +02:00
camoroso 9307473d08 Call() implementation can invoke varaibles holding functors 2024-06-04 11:03:24 +02:00
camoroso 10a596a4cd parser.go: commented out some useless code 2024-06-04 11:02:26 +02:00
camoroso 609fb21505 global-context.go: variables holding function values can be invoked as function now 2024-06-04 11:01:04 +02:00
camoroso 7650a4a441 DictType moved from operand-dict.go to the dict-type.go file 2024-06-04 10:59:25 +02:00
camoroso f51d6023ae Doc: start of function documentation 2024-06-03 06:27:14 +02:00
camoroso 99454227d5 little changes to test source files 2024-06-02 12:33:32 +02:00
camoroso 75358e5d35 func-fmt.go: print() and println() 2024-06-02 12:32:08 +02:00
camoroso 51b272dda8 func-string.go: ad least one extra prefix and suffix for startsWithStr() and endsWitchStr() 2024-06-02 12:30:17 +02:00
camoroso 7f282e5460 func-string.go: the second parameter joinStr() flags changed as pfRepeat 2024-06-02 11:56:17 +02:00
camoroso cff21b40f7 typeFilepath changed 2024-06-02 11:54:43 +02:00
camoroso 4f432da2b9 typeFilepath and typeDirpath renamed as paramFilepath and paramDirpath respectively 2024-06-02 11:32:47 +02:00
camoroso e4b4b4fb79 function.go: if the right most parameter is repeteable (pfRepeat), then it does not increment the min-arg count 2024-06-02 11:30:24 +02:00
camoroso c04678c426 simple-store.go: duplicated name removed from funcs list 2024-06-02 11:12:53 +02:00
77 changed files with 1534 additions and 1374 deletions
+1 -8
View File
@@ -10,7 +10,6 @@ import (
type Expr interface { type Expr interface {
Eval(ctx ExprContext) (result any, err error) Eval(ctx ExprContext) (result any, err error)
eval(ctx ExprContext, preset bool) (result any, err error)
String() string String() string
} }
@@ -106,16 +105,10 @@ func (self *ast) Finish() {
} }
func (self *ast) Eval(ctx ExprContext) (result any, err error) { func (self *ast) Eval(ctx ExprContext) (result any, err error) {
return self.eval(ctx, true)
}
func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
self.Finish() self.Finish()
if self.root != nil { if self.root != nil {
if preset { // initDefaultVars(ctx)
initDefaultVars(ctx)
}
if self.forest != nil { if self.forest != nil {
for _, root := range self.forest { for _, root := range self.forest {
if result, err = root.compute(ctx); err == nil { if result, err = root.compute(ctx); err == nil {
+58
View File
@@ -0,0 +1,58 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// function.go
package expr
// ---- Linking with Expr functions
type exprFunctor struct {
baseFunctor
params []ExprFuncParam
expr Expr
defCtx ExprContext
}
// func newExprFunctor(e Expr, params []string, ctx ExprContext) *exprFunctor {
// 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) {
if functor.defCtx != nil {
ctx.Merge(functor.defCtx)
}
for i, p := range functor.params {
if i < len(args) {
arg := args[i]
if funcArg, ok := arg.(Functor); ok {
// ctx.RegisterFunc(p, functor, 0, -1)
paramSpecs := funcArg.GetParams()
ctx.RegisterFunc(p.Name(), funcArg, TypeAny, paramSpecs)
} else {
ctx.UnsafeSetVar(p.Name(), arg)
}
} else {
ctx.UnsafeSetVar(p.Name(), nil)
}
}
result, err = functor.expr.Eval(ctx)
return
}
func CallExprFunction(parentCtx ExprContext, funcName string, params ...any) (v any, err error) {
ctx := cloneContext(parentCtx)
ctx.SetParent(parentCtx)
if err == nil {
if err = checkFunctionCall(ctx, funcName, &params); err == nil {
if v, err = ctx.Call(funcName, params); err == nil {
exportObjects(parentCtx, ctx)
}
}
}
return
}
+19
View File
@@ -0,0 +1,19 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// bind-go-function.go
package expr
// ---- Linking with Go functions
type golangFunctor struct {
baseFunctor
f FuncTemplate
}
func NewGolangFunctor(f FuncTemplate) *golangFunctor {
return &golangFunctor{f: f}
}
func (functor *golangFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
return functor.f(ctx, name, args)
}
+24 -24
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// func-builtins.go // builtin-base.go
package expr package expr
import ( import (
@@ -67,7 +67,7 @@ func boolFunc(ctx ExprContext, name string, args []any) (result any, err error)
case string: case string:
result = len(v) > 0 result = len(v) > 0
default: default:
err = errCantConvert(name, v, "bool") err = ErrCantConvert(name, v, "bool")
} }
return return
} }
@@ -90,7 +90,7 @@ func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = int64(i) result = int64(i)
} }
default: default:
err = errCantConvert(name, v, "int") err = ErrCantConvert(name, v, "int")
} }
return return
} }
@@ -115,7 +115,7 @@ func decFunc(ctx ExprContext, name string, args []any) (result any, err error) {
case *FractionType: case *FractionType:
result = v.toFloat() result = v.toFloat()
default: default:
err = errCantConvert(name, v, "float") err = ErrCantConvert(name, v, "float")
} }
return return
} }
@@ -127,9 +127,9 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
if len(args) > 1 { if len(args) > 1 {
var ok bool var ok bool
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 = errFuncDivisionByZero(name) err = ErrFuncDivisionByZero(name)
} }
} }
if err == nil { if err == nil {
@@ -148,7 +148,7 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
case *FractionType: case *FractionType:
result = v result = v
default: default:
err = errCantConvert(name, v, "float") err = ErrCantConvert(name, v, "float")
} }
return return
} }
@@ -159,28 +159,28 @@ func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err err
func ImportBuiltinsFuncs(ctx ExprContext) { func ImportBuiltinsFuncs(ctx ExprContext) {
anyParams := []ExprFuncParam{ anyParams := []ExprFuncParam{
newFuncParam(paramValue), NewFuncParam(ParamValue),
} }
ctx.RegisterFunc("isNil", newGolangFunctor(isNilFunc), typeBoolean, anyParams) ctx.RegisterFunc("isNil", NewGolangFunctor(isNilFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isInt", newGolangFunctor(isIntFunc), typeBoolean, anyParams) ctx.RegisterFunc("isInt", NewGolangFunctor(isIntFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isFloat", newGolangFunctor(isFloatFunc), typeBoolean, anyParams) ctx.RegisterFunc("isFloat", NewGolangFunctor(isFloatFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isBool", newGolangFunctor(isBoolFunc), typeBoolean, anyParams) ctx.RegisterFunc("isBool", NewGolangFunctor(isBoolFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isString", newGolangFunctor(isStringFunc), typeBoolean, anyParams) ctx.RegisterFunc("isString", NewGolangFunctor(isStringFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isFract", newGolangFunctor(isFractionFunc), typeBoolean, anyParams) ctx.RegisterFunc("isFract", NewGolangFunctor(isFractionFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isRational", newGolangFunctor(isRationalFunc), typeBoolean, anyParams) ctx.RegisterFunc("isRational", NewGolangFunctor(isRationalFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isList", newGolangFunctor(isListFunc), typeBoolean, anyParams) ctx.RegisterFunc("isList", NewGolangFunctor(isListFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isDict", newGolangFunctor(isDictionaryFunc), typeBoolean, anyParams) ctx.RegisterFunc("isDict", NewGolangFunctor(isDictionaryFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("bool", newGolangFunctor(boolFunc), typeBoolean, anyParams) ctx.RegisterFunc("bool", NewGolangFunctor(boolFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("int", newGolangFunctor(intFunc), typeInt, anyParams) ctx.RegisterFunc("int", NewGolangFunctor(intFunc), TypeInt, anyParams)
ctx.RegisterFunc("dec", newGolangFunctor(decFunc), typeFloat, anyParams) ctx.RegisterFunc("dec", NewGolangFunctor(decFunc), TypeFloat, anyParams)
ctx.RegisterFunc("fract", newGolangFunctor(fractFunc), typeFraction, []ExprFuncParam{ ctx.RegisterFunc("fract", NewGolangFunctor(fractFunc), TypeFraction, []ExprFuncParam{
newFuncParam(paramValue), NewFuncParam(ParamValue),
newFuncParamFlagDef("denominator", pfOptional, 1), NewFuncParamFlagDef("denominator", PfDefault, 1),
}) })
} }
func init() { func init() {
registerImport("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.") RegisterBuiltinModule("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
} }
+36
View File
@@ -0,0 +1,36 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-fmt.go
package expr
import "fmt"
func printFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var n int
if n, err = fmt.Print(args...); err == nil {
result = int64(n)
}
return
}
func printLnFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var n int
if n, err = fmt.Println(args...); err == nil {
result = int64(n)
}
return
}
func ImportFmtFuncs(ctx ExprContext) {
ctx.RegisterFunc("print", NewGolangFunctor(printFunc), TypeInt, []ExprFuncParam{
NewFuncParamFlag(ParamItem, PfRepeat),
})
ctx.RegisterFunc("println", NewGolangFunctor(printLnFunc), TypeInt, []ExprFuncParam{
NewFuncParamFlag(ParamItem, PfRepeat),
})
}
func init() {
RegisterBuiltinModule("fmt", ImportFmtFuncs, "String and console formatting functions")
}
+75
View File
@@ -0,0 +1,75 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-import.go
package expr
import (
"io"
"os"
)
func importFunc(ctx ExprContext, name string, args []any) (result any, err error) {
return importGeneral(ctx, name, args)
}
func importAllFunc(ctx ExprContext, name string, args []any) (result any, err error) {
CtrlEnable(ctx, control_export_all)
return importGeneral(ctx, name, args)
}
func importGeneral(ctx ExprContext, name string, args []any) (result any, err error) {
dirList := buildSearchDirList("sources", ENV_EXPR_SOURCE_PATH)
result, err = doImport(ctx, name, dirList, NewArrayIterator(args))
return
}
func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (result any, err error) {
var v any
var sourceFilepath string
for v, err = it.Next(); err == nil; v, err = it.Next() {
if err = checkStringParamExpected(name, v, it.Index()); err != nil {
break
}
if sourceFilepath, err = makeFilepath(v.(string), dirList); err != nil {
break
}
var file *os.File
if file, err = os.Open(sourceFilepath); err == nil {
defer file.Close()
var expr *ast
scanner := NewScanner(file, DefaultTranslations())
parser := NewParser()
if expr, err = parser.parseGeneral(scanner, true, true); err == nil {
result, err = expr.Eval(ctx)
}
if err != nil {
break
}
} else {
break
}
}
if err != nil {
if err == io.EOF {
err = nil
} else {
result = nil
}
}
return
}
func ImportImportFuncs(ctx ExprContext) {
ctx.RegisterFunc("import", NewGolangFunctor(importFunc), TypeAny, []ExprFuncParam{
NewFuncParamFlag(ParamFilepath, PfRepeat),
})
ctx.RegisterFunc("importAll", NewGolangFunctor(importAllFunc), TypeAny, []ExprFuncParam{
NewFuncParamFlag(ParamFilepath, PfRepeat),
})
}
func init() {
RegisterBuiltinModule("import", ImportImportFuncs, "Functions import() and include()")
}
+6 -6
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// funcs-math.go // builtin-math-arith.go
package expr package expr
import ( import (
@@ -167,15 +167,15 @@ 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, int64(0)), NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, int64(0)),
}) })
ctx.RegisterFunc("mul", &golangFunctor{f: mulFunc}, typeNumber, []ExprFuncParam{ ctx.RegisterFunc("mul", &golangFunctor{f: mulFunc}, TypeNumber, []ExprFuncParam{
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, int64(1)), NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, int64(1)),
}) })
} }
func init() { func init() {
registerImport("math.arith", ImportMathFuncs, "Functions add() and mul()") RegisterBuiltinModule("math.arith", ImportMathFuncs, "Functions add() and mul()")
} }
+17 -17
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// func-os.go // builtin-os-file.go
package expr package expr
import ( import (
@@ -92,7 +92,7 @@ func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err e
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)} result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
} }
} else { } else {
err = errMissingFilePath("openFile") err = errMissingFilePath("appendFile")
} }
return return
} }
@@ -187,28 +187,28 @@ func readFileFunc(ctx ExprContext, name string, args []any) (result any, err err
} }
func ImportOsFuncs(ctx ExprContext) { func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("openFile", newGolangFunctor(openFileFunc), typeHandle, []ExprFuncParam{ ctx.RegisterFunc("fileOpen", NewGolangFunctor(openFileFunc), TypeHandle, []ExprFuncParam{
newFuncParam(typeFilepath), NewFuncParam(ParamFilepath),
}) })
ctx.RegisterFunc("appendFile", newGolangFunctor(appendFileFunc), typeHandle, []ExprFuncParam{ ctx.RegisterFunc("fileAppend", NewGolangFunctor(appendFileFunc), TypeHandle, []ExprFuncParam{
newFuncParam(typeFilepath), NewFuncParam(ParamFilepath),
}) })
ctx.RegisterFunc("createFile", newGolangFunctor(createFileFunc), typeHandle, []ExprFuncParam{ ctx.RegisterFunc("fileCreate", NewGolangFunctor(createFileFunc), TypeHandle, []ExprFuncParam{
newFuncParam(typeFilepath), NewFuncParam(ParamFilepath),
}) })
ctx.RegisterFunc("writeFile", newGolangFunctor(writeFileFunc), typeInt, []ExprFuncParam{ ctx.RegisterFunc("fileWrite", NewGolangFunctor(writeFileFunc), TypeInt, []ExprFuncParam{
newFuncParam(typeHandle), NewFuncParam(TypeHandle),
newFuncParamFlagDef(typeItem, pfOptional|pfRepeat, ""), NewFuncParamFlagDef(TypeItem, PfDefault|PfRepeat, ""),
}) })
ctx.RegisterFunc("readFile", newGolangFunctor(readFileFunc), typeString, []ExprFuncParam{ ctx.RegisterFunc("fileRead", NewGolangFunctor(readFileFunc), TypeString, []ExprFuncParam{
newFuncParam(typeHandle), NewFuncParam(TypeHandle),
newFuncParamFlagDef("limitCh", pfOptional, "\n"), NewFuncParamFlagDef("limitCh", PfDefault, "\n"),
}) })
ctx.RegisterFunc("closeFile", newGolangFunctor(closeFileFunc), typeBoolean, []ExprFuncParam{ ctx.RegisterFunc("fileClose", NewGolangFunctor(closeFileFunc), TypeBoolean, []ExprFuncParam{
newFuncParam(typeHandle), NewFuncParam(TypeHandle),
}) })
} }
func init() { func init() {
registerImport("os.file", ImportOsFuncs, "Operating system file functions") RegisterBuiltinModule("os.file", ImportOsFuncs, "Operating system file functions")
} }
+33 -31
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// func-string.go // builtin-string.go
package expr package expr
import ( import (
@@ -21,7 +21,7 @@ func doJoinStr(funcName string, sep string, it Iterator) (result any, err error)
if s, ok := v.(string); ok { if s, ok := v.(string); ok {
sb.WriteString(s) sb.WriteString(s)
} else { } else {
err = errExpectedGot(funcName, typeString, v) err = ErrExpectedGot(funcName, TypeString, v)
return return
} }
} }
@@ -45,13 +45,13 @@ func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err erro
} else if it, ok := args[1].(Iterator); ok { } else if it, ok := args[1].(Iterator); ok {
result, err = doJoinStr(name, sep, it) result, err = doJoinStr(name, sep, it)
} else { } else {
err = errInvalidParameterValue(name, paramParts, args[1]) err = ErrInvalidParameterValue(name, ParamParts, args[1])
} }
} else { } else {
result, err = doJoinStr(name, sep, NewArrayIterator(args[1:])) result, err = doJoinStr(name, sep, NewArrayIterator(args[1:]))
} }
} else { } else {
err = errWrongParamType(name, paramSeparator, typeString, args[0]) err = ErrWrongParamType(name, ParamSeparator, TypeString, args[0])
} }
return return
} }
@@ -63,14 +63,14 @@ func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error
var ok bool var ok bool
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 start, err = toInt(args[1], name+"()"); err != nil { if start, err = ToInt(args[1], name+"()"); err != nil {
return return
} }
if count, err = toInt(args[2], name+"()"); err != nil { if count, err = ToInt(args[2], name+"()"); err != nil {
return return
} }
@@ -91,7 +91,7 @@ func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err erro
var ok bool var ok bool
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])
} }
result = strings.TrimSpace(source) result = strings.TrimSpace(source)
return return
@@ -104,7 +104,7 @@ func startsWithStrFunc(ctx ExprContext, name string, args []any) (result any, er
result = false result = false
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])
} }
for i, targetSpec := range args[1:] { for i, targetSpec := range args[1:] {
if target, ok := targetSpec.(string); ok { if target, ok := targetSpec.(string); ok {
@@ -127,7 +127,7 @@ func endsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err
result = false result = false
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])
} }
for i, targetSpec := range args[1:] { for i, targetSpec := range args[1:] {
if target, ok := targetSpec.(string); ok { if target, ok := targetSpec.(string); ok {
@@ -150,7 +150,7 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
var ok bool var ok bool
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 sep, ok = args[1].(string); !ok { if sep, ok = args[1].(string); !ok {
@@ -182,40 +182,42 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
// Import above functions in the context // Import above functions in the context
func ImportStringFuncs(ctx ExprContext) { func ImportStringFuncs(ctx ExprContext) {
ctx.RegisterFunc("joinStr", newGolangFunctor(joinStrFunc), typeString, []ExprFuncParam{ ctx.RegisterFunc("strJoin", NewGolangFunctor(joinStrFunc), TypeString, []ExprFuncParam{
newFuncParam(paramSeparator), NewFuncParam(ParamSeparator),
newFuncParamFlagDef(paramItem, pfOptional|pfRepeat, ""), NewFuncParamFlag(ParamItem, PfRepeat),
}) })
ctx.RegisterFunc("subStr", newGolangFunctor(subStrFunc), typeString, []ExprFuncParam{ ctx.RegisterFunc("strSub", NewGolangFunctor(subStrFunc), TypeString, []ExprFuncParam{
newFuncParam(paramSource), NewFuncParam(ParamSource),
newFuncParamFlagDef(paramStart, pfOptional, int64(0)), NewFuncParamFlagDef(ParamStart, PfDefault, int64(0)),
newFuncParamFlagDef(paramCount, pfOptional, int64(-1)), NewFuncParamFlagDef(ParamCount, PfDefault, int64(-1)),
}) })
ctx.RegisterFunc("splitStr", newGolangFunctor(splitStrFunc), "list of "+typeString, []ExprFuncParam{ ctx.RegisterFunc("strSplit", NewGolangFunctor(splitStrFunc), "list of "+TypeString, []ExprFuncParam{
newFuncParam(paramSource), NewFuncParam(ParamSource),
newFuncParamFlagDef(paramSeparator, pfOptional, ""), NewFuncParamFlagDef(ParamSeparator, PfDefault, ""),
newFuncParamFlagDef(paramCount, pfOptional, int64(-1)), NewFuncParamFlagDef(ParamCount, PfDefault, int64(-1)),
}) })
ctx.RegisterFunc("trimStr", newGolangFunctor(trimStrFunc), typeString, []ExprFuncParam{ ctx.RegisterFunc("strTrim", NewGolangFunctor(trimStrFunc), TypeString, []ExprFuncParam{
newFuncParam(paramSource), NewFuncParam(ParamSource),
}) })
ctx.RegisterFunc("startsWithStr", newGolangFunctor(startsWithStrFunc), typeBoolean, []ExprFuncParam{ ctx.RegisterFunc("strStartsWith", NewGolangFunctor(startsWithStrFunc), TypeBoolean, []ExprFuncParam{
newFuncParam(paramSource), NewFuncParam(ParamSource),
newFuncParamFlag(paramPrefix, pfRepeat), NewFuncParam(ParamPrefix),
NewFuncParamFlag("other "+ParamPrefix, PfRepeat),
}) })
ctx.RegisterFunc("endsWithStr", newGolangFunctor(endsWithStrFunc), typeBoolean, []ExprFuncParam{ ctx.RegisterFunc("strEndsWith", NewGolangFunctor(endsWithStrFunc), TypeBoolean, []ExprFuncParam{
newFuncParam(paramSource), NewFuncParam(ParamSource),
newFuncParamFlag(paramSuffix, pfRepeat), NewFuncParam(ParamSuffix),
NewFuncParamFlag("other "+ParamSuffix, PfRepeat),
}) })
} }
// Register the import function in the import-register. // Register the import function in the import-register.
// That will allow to import all function of this module by the "builtin" operator." // That will allow to import all function of this module by the "builtin" operator."
func init() { func init() {
registerImport("string", ImportStringFuncs, "string utilities") RegisterBuiltinModule("string", ImportStringFuncs, "string utilities")
} }
+48
View File
@@ -0,0 +1,48 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtins-register.go
package expr
import (
"fmt"
)
type builtinModule struct {
importFunc func(ExprContext)
description string
imported bool
}
func newBuiltinModule(importFunc func(ExprContext), description string) *builtinModule {
return &builtinModule{importFunc, description, false}
}
var builtinModuleRegister map[string]*builtinModule
func RegisterBuiltinModule(name string, importFunc func(ExprContext), description string) {
if builtinModuleRegister == nil {
builtinModuleRegister = make(map[string]*builtinModule)
}
if _, exists := builtinModuleRegister[name]; exists {
panic(fmt.Errorf("module %q already registered", name))
}
builtinModuleRegister[name] = newBuiltinModule(importFunc, description)
}
func IterateBuiltinModules(op func(name, description string, imported bool) bool) {
if op != nil {
for name, mod := range builtinModuleRegister {
if !op(name, mod.description, mod.imported) {
break
}
}
}
}
// ----
func init() {
if builtinModuleRegister == nil {
builtinModuleRegister = make(map[string]*builtinModule)
}
}
+14 -22
View File
@@ -8,7 +8,7 @@ import (
"fmt" "fmt"
) )
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)
} else { } else {
@@ -17,47 +17,39 @@ func errTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error
return return
} }
func errTooMuchParams(funcName string, maxArgs, argCount int) (err error) { func ErrTooMuchParams(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 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 {
if typer, ok := value.(Typer); ok { return fmt.Errorf("%s(): can't convert %s to %s", funcName, TypeName(value), kind)
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 %s (%v)", funcName, kind, TypeName(value), value)
} }
func errFuncDivisionByZero(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 { func ErrDivisionByZero() error {
return fmt.Errorf("division by zero") return fmt.Errorf("division by zero")
} }
// --- Parameter errors // --- Parameter errors
// func errOneParam(funcName string) error { func ErrMissingRequiredParameter(funcName, paramName string) error {
// return fmt.Errorf("%s() requires exactly one param", funcName) return fmt.Errorf("%s(): missing required parameter %q", funcName, paramName)
// }
func errMissingRequiredParameter(funcName, paramName string) error {
return fmt.Errorf("%s() missing required parameter %q", funcName, paramName)
} }
func errInvalidParameterValue(funcName, paramName string, paramValue any) error { func ErrInvalidParameterValue(funcName, paramName string, paramValue any) error {
return fmt.Errorf("%s() invalid value %T (%v) for parameter %q", funcName, paramValue, paramValue, paramName) return fmt.Errorf("%s(): invalid value %s (%v) for parameter %q", funcName, TypeName(paramValue), paramValue, paramName)
} }
func errWrongParamType(funcName, paramName, paramType string, paramValue any) error { func ErrWrongParamType(funcName, paramName, paramType string, paramValue any) error {
return fmt.Errorf("%s() the %q parameter must be a %s, got a %T (%v)", funcName, paramName, paramType, paramValue, paramValue) return fmt.Errorf("%s(): the %q parameter must be a %s, got a %s (%v)", funcName, paramName, paramType, TypeName(paramValue), paramValue)
} }
+13 -22
View File
@@ -5,26 +5,17 @@
package expr package expr
const ( const (
paramCount = "count" ParamCount = "count"
paramItem = "item" ParamItem = "item"
paramParts = "parts" ParamParts = "parts"
paramSeparator = "separator" ParamSeparator = "separator"
paramSource = "source" ParamSource = "source"
paramSuffix = "suffix" ParamSuffix = "suffix"
paramPrefix = "prefix" ParamPrefix = "prefix"
paramStart = "start" ParamStart = "start"
paramEnd = "end" ParamEnd = "end"
paramValue = "value" ParamValue = "value"
paramEllipsis = "..." ParamEllipsis = "..."
typeFilepath = "filepath" ParamFilepath = "filepath"
typeDirpath = "dirpath" ParamDirpath = "dirpath"
) )
// const (
// typeInteger = "int"
// typeFloat = "float"
// typeString = "string"
// typeFraction = "fract"
// typeList = "list"
// typeDict = "dict"
// )
+12 -10
View File
@@ -5,14 +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" TypeHandle = "handle"
typeInt = "integer" TypeInt = "integer"
typeItem = "item" TypeItem = "item"
typeNumber = "number" TypeNumber = "number"
typePair = "pair" TypePair = "pair"
typeString = "string" TypeString = "string"
TypeListOf = "list-of-"
TypeListOfStrings = "list-of-strings"
) )
+1 -3
View File
@@ -22,13 +22,11 @@ func exportFunc(ctx ExprContext, name string, info ExprFunc) {
if name[0] == '@' { if name[0] == '@' {
name = name[1:] name = name[1:]
} }
// ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
// ctx.RegisterFuncInfo(name, info)
ctx.RegisterFunc(name, info.Functor(), info.ReturnType(), info.Params()) ctx.RegisterFunc(name, info.Functor(), info.ReturnType(), info.Params())
} }
func exportObjects(destCtx, sourceCtx ExprContext) { func exportObjects(destCtx, sourceCtx ExprContext) {
exportAll := isEnabled(sourceCtx, control_export_all) exportAll := CtrlIsEnabled(sourceCtx, control_export_all)
// fmt.Printf("Exporting from sourceCtx [%p] to destCtx [%p] -- exportAll=%t\n", sourceCtx, destCtx, exportAll) // fmt.Printf("Exporting from sourceCtx [%p] to destCtx [%p] -- exportAll=%t\n", sourceCtx, destCtx, exportAll)
// Export variables // Export variables
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) { for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
+3
View File
@@ -16,6 +16,7 @@ type Functor interface {
type ExprFuncParam interface { type ExprFuncParam interface {
Name() string Name() string
Type() string Type() string
IsDefault() bool
IsOptional() bool IsOptional() bool
IsRepeat() bool IsRepeat() bool
DefaultValue() any DefaultValue() any
@@ -36,6 +37,8 @@ type ExprFunc interface {
type ExprContext interface { type ExprContext interface {
Clone() ExprContext Clone() ExprContext
Merge(ctx ExprContext) Merge(ctx ExprContext)
SetParent(ctx ExprContext)
GetParent() (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)
+9 -38
View File
@@ -4,13 +4,13 @@
// control.go // control.go
package expr package expr
import "strings"
// Preset control variables // Preset control variables
const ( const (
ControlPreset = "_preset"
ControlLastResult = "last" ControlLastResult = "last"
ControlBoolShortcut = "_bool_shortcut" ControlBoolShortcut = "_bool_shortcut"
ControlImportPath = "_import_path" ControlSearchPath = "_search_path"
ControlParentContext = "_parent_context"
) )
// Other control variables // Other control variables
@@ -20,43 +20,14 @@ const (
// Initial values // Initial values
const ( const (
init_import_path = "~/.local/lib/go-pkg/expr:/usr/local/lib/go-pkg/expr:/usr/lib/go-pkg/expr" init_search_path = "~/.local/lib/go-pkg/expr:/usr/local/lib/go-pkg/expr:/usr/lib/go-pkg/expr"
) )
func initDefaultVars(ctx ExprContext) { func initDefaultVars(ctx ExprContext) {
if _, exists := ctx.GetVar(ControlPreset); exists {
return
}
ctx.SetVar(ControlPreset, true)
ctx.SetVar(ControlBoolShortcut, true) ctx.SetVar(ControlBoolShortcut, true)
ctx.SetVar(ControlImportPath, init_import_path) ctx.SetVar(ControlSearchPath, init_search_path)
}
func enable(ctx ExprContext, name string) {
if strings.HasPrefix(name, "_") {
ctx.SetVar(name, true)
} else {
ctx.SetVar("_"+name, true)
}
}
func disable(ctx ExprContext, name string) {
if strings.HasPrefix(name, "_") {
ctx.SetVar(name, false)
} else {
ctx.SetVar("_"+name, false)
}
}
func isEnabled(ctx ExprContext, name string) (status bool) {
if v, exists := ctx.GetVar(name); exists {
if b, ok := v.(bool); ok {
status = b
}
}
return
}
func getControlString(ctx ExprContext, name string) (s string, exists bool) {
var v any
if v, exists = ctx.GetVar(name); exists {
s, exists = v.(string)
}
return
} }
+151
View File
@@ -0,0 +1,151 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// dict-type.go
package expr
import (
"fmt"
"reflect"
"strings"
)
type DictType map[any]any
func MakeDict() (dict *DictType) {
d := make(DictType)
dict = &d
return
}
func newDict(dictAny map[any]*term) (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 (dict *DictType) toMultiLine(sb *strings.Builder, opt FmtOpt) {
indent := GetFormatIndent(opt)
flags := GetFormatFlags(opt)
//sb.WriteString(strings.Repeat(" ", indent))
sb.WriteByte('{')
if len(*dict) > 0 {
innerOpt := MakeFormatOptions(flags, indent+1)
nest := strings.Repeat(" ", indent+1)
sb.WriteByte('\n')
first := true
for name, value := range *dict {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
sb.WriteString(nest)
if key, ok := name.(string); ok {
sb.WriteString(string('"') + key + string('"'))
} else {
sb.WriteString(fmt.Sprintf("%v", name))
}
sb.WriteString(": ")
if f, ok := value.(Formatter); ok {
sb.WriteString(f.ToString(innerOpt))
} else if _, ok = value.(Functor); ok {
sb.WriteString("func(){}")
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteByte('\n')
sb.WriteString(strings.Repeat(" ", indent))
}
sb.WriteString("}")
}
func (dict *DictType) ToString(opt FmtOpt) string {
var sb strings.Builder
flags := GetFormatFlags(opt)
if flags&MultiLine != 0 {
dict.toMultiLine(&sb, opt)
} else {
sb.WriteByte('{')
first := true
for key, value := range *dict {
if first {
first = false
} else {
sb.WriteString(", ")
}
if s, ok := key.(string); ok {
sb.WriteString(string('"') + s + string('"'))
} else {
sb.WriteString(fmt.Sprintf("%v", key))
}
sb.WriteString(": ")
if formatter, ok := value.(Formatter); ok {
sb.WriteString(formatter.ToString(opt))
} else if t, ok := value.(*term); ok {
sb.WriteString(t.String())
} else {
sb.WriteString(fmt.Sprintf("%#v", value))
}
}
sb.WriteByte('}')
}
return sb.String()
}
func (dict *DictType) String() string {
return dict.ToString(0)
}
func (dict *DictType) TypeName() string {
return "dict"
}
func (dict *DictType) hasKey(target any) (ok bool) {
for key := range *dict {
if ok = reflect.DeepEqual(key, target); ok {
break
}
}
return
}
func (dict *DictType) clone() (c *DictType) {
c = newDict(nil)
for k, v := range *dict {
(*c)[k] = v
}
return
}
func (dict *DictType) merge(second *DictType) {
if second != nil {
for k, v := range *second {
(*dict)[k] = v
}
}
}
func (dict *DictType) setItem(key any, value any) (err error) {
(*dict)[key] = value
return
}
////////////////
type DictFormat interface {
ToDict() *DictType
}
+5 -2
View File
@@ -22,7 +22,7 @@ Expressions calculator
toc::[] toc::[]
#TODO: Work in progress (last update on 2024/05/20, 06:58 a.m.)# #TODO: Work in progress (last update on 2024/06/02, 08:18 a.m.)#
== Expr == Expr
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions. _Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
@@ -615,7 +615,10 @@ The table below shows all supported operators by decreasing priorities.
|=== |===
== Functions == Functions
Functions in _Expr_ are very similar to functions in many programming languages. Functions in _Expr_ are very similar to functions available in many programming languages. Actually, _Expr_ supports two types of function, _expr-functions_ and _go-functions_.
* _expr-functions_ are defined using _Expr_'s syntax. They can be passed as arguments to other functions and can be returned from functions. Moreover, they bind themselves to the defining context, thus becoming closures.
* _go-functions_ are regular Golang functions callable from _Expr_ expressions. They are defined in Golang source files called _modules_ and compiled within the _Expr_ package. To make Golang functions available in _Expr_ contextes, it is required to _import_ the module in which they are defined.
In _Expr_ functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator [blue]`@` it is possibile to export local definition to the calling context. In _Expr_ functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator [blue]`@` it is possibile to export local definition to the calling context.
+14 -4
View File
@@ -585,7 +585,7 @@ pre.rouge .ss {
<div class="sectionbody"> <div class="sectionbody">
<!-- toc disabled --> <!-- toc disabled -->
<div class="paragraph"> <div class="paragraph">
<p><mark>TODO: Work in progress (last update on 2024/05/20, 06:58 a.m.)</mark></p> <p><mark>TODO: Work in progress (last update on 2024/06/02, 08:18 a.m.)</mark></p>
</div> </div>
</div> </div>
</div> </div>
@@ -1609,7 +1609,7 @@ These operators have a high priority, in particular higher than the operator <co
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">.</code></p></td> <td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">.</code></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td> <td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Dict item</em></p></td> <td class="tableblock halign-center valign-top"><p class="tableblock"><em>Dict item</em></p></td>
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>dict</em> <code>""</code> <em>any</em> &#8594; <em>any</em></p></td> <td class="tableblock halign-center valign-top"><p class="tableblock"><em>dict</em> <code>"."</code> <em>any</em> &#8594; <em>any</em></p></td>
</tr> </tr>
<tr> <tr>
<td class="tableblock halign-center valign-top" rowspan="2"><p class="tableblock"><strong>INC</strong></p></td> <td class="tableblock halign-center valign-top" rowspan="2"><p class="tableblock"><strong>INC</strong></p></td>
@@ -1834,7 +1834,17 @@ These operators have a high priority, in particular higher than the operator <co
<h2 id="_functions"><a class="anchor" href="#_functions"></a><a class="link" href="#_functions">6. Functions</a></h2> <h2 id="_functions"><a class="anchor" href="#_functions"></a><a class="link" href="#_functions">6. Functions</a></h2>
<div class="sectionbody"> <div class="sectionbody">
<div class="paragraph"> <div class="paragraph">
<p>Functions in <em>Expr</em> are very similar to functions in many programming languages.</p> <p>Functions in <em>Expr</em> are very similar to functions available in many programming languages. Actually, <em>Expr</em> supports two types of function, <em>expr-functions</em> and <em>go-functions</em>.</p>
</div>
<div class="ulist">
<ul>
<li>
<p><em>expr-functions</em> are defined using <em>Expr</em>'s syntax. They can be passed as arguments to other functions and can be returned from functions. Moreover, they bind themselves to the defining context, thus becoming closures.</p>
</li>
<li>
<p><em>go-functions</em> are regular Golang functions callable from <em>Expr</em> expressions. They are defined in Golang source files called <em>modules</em> and compiled within the <em>Expr</em> package. To make Golang functions available in <em>Expr</em> contextes, it is required to <em>import</em> the module in which they are defined.</p>
</li>
</ul>
</div> </div>
<div class="paragraph"> <div class="paragraph">
<p>In <em>Expr</em> functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator <code class="blue">@</code> it is possibile to export local definition to the calling context.</p> <p>In <em>Expr</em> functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator <code class="blue">@</code> it is possibile to export local definition to the calling context.</p>
@@ -1874,7 +1884,7 @@ These operators have a high priority, in particular higher than the operator <co
</div> </div>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated 2024-05-20 09:40:23 +0200 Last updated 2024-06-03 06:26:03 +0200
</div> </div>
</div> </div>
</body> </body>
+17 -3
View File
@@ -6,7 +6,7 @@ package expr
import "fmt" import "fmt"
type FmtOpt uint16 type FmtOpt uint32 // lower 16 bits hold a bit-mask, higher 16 bits hold an indentation number
const ( const (
TTY FmtOpt = 1 << iota TTY FmtOpt = 1 << iota
@@ -30,6 +30,18 @@ func TruncateString(s string) (trunc string) {
return return
} }
func MakeFormatOptions(flags FmtOpt, indent int) FmtOpt {
return FmtOpt(indent<<16) | flags
}
func GetFormatFlags(opt FmtOpt) FmtOpt {
return opt & 0xFFFF
}
func GetFormatIndent(opt FmtOpt) int {
return int(opt >> 16)
}
type Formatter interface { type Formatter interface {
ToString(options FmtOpt) string ToString(options FmtOpt) string
} }
@@ -51,8 +63,10 @@ type Typer interface {
TypeName() string TypeName() string
} }
func getTypeName(v any) (name string) { func TypeName(v any) (name string) {
if typer, ok := v.(Typer); ok { if v == nil {
name = "nil"
} else if typer, ok := v.(Typer); ok {
name = typer.TypeName() name = typer.TypeName()
} else if IsInteger(v) { } else if IsInteger(v) {
name = "integer" name = "integer"
+4 -46
View File
@@ -33,52 +33,10 @@ func float64ToFraction(f float64) (fract *FractionType, err error) {
} }
dec := fmt.Sprintf("%.12f", decPart) dec := fmt.Sprintf("%.12f", decPart)
s := fmt.Sprintf("%s%.f%s", sign, intPart, dec[1:]) s := fmt.Sprintf("%s%.f%s", sign, intPart, dec[1:])
// fmt.Printf("S: '%s'\n",s)
return makeGeneratingFraction(s) return makeGeneratingFraction(s)
} }
// Based on https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/math/big/rat.go;l=39 // Based on https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/math/big/rat.go;l=39
/*
func _float64ToFraction(f float64) (num, den int64, err error) {
const expMask = 1<<11 - 1
bits := math.Float64bits(f)
mantissa := bits & (1<<52 - 1)
exp := int((bits >> 52) & expMask)
switch exp {
case expMask: // non-finite
err = errors.New("infite")
return
case 0: // denormal
exp -= 1022
default: // normal
mantissa |= 1 << 52
exp -= 1023
}
shift := 52 - exp
// Optimization (?): partially pre-normalise.
for mantissa&1 == 0 && shift > 0 {
mantissa >>= 1
shift--
}
if f < 0 {
num = -int64(mantissa)
} else {
num = int64(mantissa)
}
den = int64(1)
if shift > 0 {
den = den << shift
} else {
num = num << (-shift)
}
return
}
*/
func makeGeneratingFraction(s string) (f *FractionType, err error) { func makeGeneratingFraction(s string) (f *FractionType, err error) {
var num, den int64 var num, den int64
var sign int64 = 1 var sign int64 = 1
@@ -112,7 +70,7 @@ func makeGeneratingFraction(s string) (f *FractionType, err error) {
} }
for _, c := range dec[0:lsd] { for _, c := range dec[0:lsd] {
if c < '0' || c > '9' { if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c) return nil, ErrExpectedGot("fract", "digit", c)
} }
num = num*10 + int64(c-'0') num = num*10 + int64(c-'0')
den = den * 10 den = den * 10
@@ -123,7 +81,7 @@ func makeGeneratingFraction(s string) (f *FractionType, err error) {
mul := int64(1) mul := int64(1)
for _, c := range subParts[0] { for _, c := range subParts[0] {
if c < '0' || c > '9' { if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c) return nil, ErrExpectedGot("fract", "digit", c)
} }
num = num*10 + int64(c-'0') num = num*10 + int64(c-'0')
sub = sub*10 + int64(c-'0') sub = sub*10 + int64(c-'0')
@@ -136,7 +94,7 @@ func makeGeneratingFraction(s string) (f *FractionType, err error) {
p := subParts[1][0 : len(subParts[1])-1] p := subParts[1][0 : len(subParts[1])-1]
for _, c := range p { for _, c := range p {
if c < '0' || c > '9' { if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c) return nil, ErrExpectedGot("fract", "digit", c)
} }
num = num*10 + int64(c-'0') num = num*10 + int64(c-'0')
den = den*10 + 9 den = den*10 + 9
@@ -254,7 +212,7 @@ func anyToFract(v any) (f *FractionType, err error) {
} }
} }
if f == nil { if f == nil {
err = errExpectedGot("fract", typeFraction, v) err = ErrExpectedGot("fract", TypeFraction, v)
} }
return return
} }
-150
View File
@@ -1,150 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-import.go
package expr
import (
"errors"
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
)
const ENV_EXPR_PATH = "EXPR_PATH"
func importFunc(ctx ExprContext, name string, args []any) (result any, err error) {
return importGeneral(ctx, name, args)
}
func importAllFunc(ctx ExprContext, name string, args []any) (result any, err error) {
enable(ctx, control_export_all)
return importGeneral(ctx, name, args)
}
func importGeneral(ctx ExprContext, name string, args []any) (result any, err error) {
var dirList []string
dirList = addEnvImportDirs(dirList)
dirList = addPresetImportDirs(ctx, dirList)
result, err = doImport(ctx, name, dirList, NewArrayIterator(args))
return
}
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(IsString(paramValue) /*|| isList(paramValue)*/) {
err = fmt.Errorf("%s(): param nr %d has wrong type %T, string expected", funcName, paramPos+1, paramValue)
}
return
}
func addEnvImportDirs(dirList []string) []string {
if dirSpec, exists := os.LookupEnv(ENV_EXPR_PATH); exists {
dirs := strings.Split(dirSpec, ":")
if dirList == nil {
dirList = dirs
} else {
dirList = append(dirList, dirs...)
}
}
return dirList
}
func addPresetImportDirs(ctx ExprContext, dirList []string) []string {
if dirSpec, exists := getControlString(ctx, ControlImportPath); exists {
dirs := strings.Split(dirSpec, ":")
if dirList == nil {
dirList = dirs
} else {
dirList = append(dirList, dirs...)
}
}
return dirList
}
func isFile(filePath string) bool {
info, err := os.Stat(filePath)
return (err == nil || errors.Is(err, os.ErrExist)) && info.Mode().IsRegular()
}
func searchAmongPath(filename string, dirList []string) (filePath string) {
for _, dir := range dirList {
if fullPath := path.Join(dir, filename); isFile(fullPath) {
filePath = fullPath
break
}
}
return
}
func isPathRelative(filePath string) bool {
unixPath := filepath.ToSlash(filePath)
return strings.HasPrefix(unixPath, "./") || strings.HasPrefix(unixPath, "../")
}
func makeFilepath(filename string, dirList []string) (filePath string, err error) {
if path.IsAbs(filename) || isPathRelative(filename) {
if isFile(filename) {
filePath = filename
}
} else {
filePath = searchAmongPath(filename, dirList)
}
if len(filePath) == 0 {
err = fmt.Errorf("source file %q not found", filename)
}
return
}
func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (result any, err error) {
var v any
var sourceFilepath string
for v, err = it.Next(); err == nil; v, err = it.Next() {
if err = checkStringParamExpected(name, v, it.Index()); err != nil {
break
}
if sourceFilepath, err = makeFilepath(v.(string), dirList); err != nil {
break
}
var file *os.File
if file, err = os.Open(sourceFilepath); err == nil {
defer file.Close()
var expr *ast
scanner := NewScanner(file, DefaultTranslations())
parser := NewParser(ctx)
if expr, err = parser.parseGeneral(scanner, true, true); err == nil {
result, err = expr.eval(ctx, false)
}
if err != nil {
break
}
} else {
break
}
}
if err != nil {
if err == io.EOF {
err = nil
} else {
result = nil
}
}
return
}
func ImportImportFuncs(ctx ExprContext) {
ctx.RegisterFunc("import", newGolangFunctor(importFunc), typeAny, []ExprFuncParam{
newFuncParamFlag(typeFilepath, pfRepeat),
})
ctx.RegisterFunc("importAll", newGolangFunctor(importAllFunc), typeAny, []ExprFuncParam{
newFuncParamFlag(typeFilepath, pfRepeat),
})
}
func init() {
registerImport("import", ImportImportFuncs, "Functions import() and include()")
}
+20 -67
View File
@@ -21,7 +21,7 @@ func (functor *baseFunctor) ToString(opt FmtOpt) (s string) {
if functor.info != nil { if functor.info != nil {
s = functor.info.ToString(opt) s = functor.info.ToString(opt)
} else { } else {
s = "func() {<body>}" s = "func() {}"
} }
return s return s
} }
@@ -42,65 +42,13 @@ func (functor *baseFunctor) GetFunc() ExprFunc {
return functor.info return functor.info
} }
// ---- Linking with Go functions
type golangFunctor struct {
baseFunctor
f FuncTemplate
}
func newGolangFunctor(f FuncTemplate) *golangFunctor {
return &golangFunctor{f: f}
}
func (functor *golangFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
return functor.f(ctx, name, args)
}
// ---- Linking with Expr functions
type exprFunctor struct {
baseFunctor
params []ExprFuncParam
expr Expr
defCtx ExprContext
}
// func newExprFunctor(e Expr, params []string, ctx ExprContext) *exprFunctor {
// 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) {
if functor.defCtx != nil {
ctx.Merge(functor.defCtx)
}
for i, p := range functor.params {
if i < len(args) {
arg := args[i]
if funcArg, ok := arg.(Functor); ok {
// ctx.RegisterFunc(p, functor, 0, -1)
paramSpecs := funcArg.GetParams()
ctx.RegisterFunc(p.Name(), funcArg, typeAny, paramSpecs)
} else {
ctx.UnsafeSetVar(p.Name(), arg)
}
} else {
ctx.UnsafeSetVar(p.Name(), nil)
}
}
result, err = functor.expr.eval(ctx, false)
return
}
// ---- Function Parameters // ---- Function Parameters
type paramFlags uint16 type paramFlags uint16
const ( const (
pfOptional paramFlags = 1 << iota PfDefault paramFlags = 1 << iota
pfRepeat PfOptional
PfRepeat
) )
type funcParamInfo struct { type funcParamInfo struct {
@@ -109,15 +57,15 @@ type funcParamInfo struct {
defaultValue any defaultValue any
} }
func newFuncParam(name string) ExprFuncParam { func NewFuncParam(name string) ExprFuncParam {
return &funcParamInfo{name: name} return &funcParamInfo{name: name}
} }
func newFuncParamFlag(name string, flags paramFlags) ExprFuncParam { func NewFuncParamFlag(name string, flags paramFlags) ExprFuncParam {
return &funcParamInfo{name: name, flags: flags} return &funcParamInfo{name: name, flags: flags}
} }
func newFuncParamFlagDef(name string, flags paramFlags, defValue any) *funcParamInfo { func NewFuncParamFlagDef(name string, flags paramFlags, defValue any) *funcParamInfo {
return &funcParamInfo{name: name, flags: flags, defaultValue: defValue} return &funcParamInfo{name: name, flags: flags, defaultValue: defValue}
} }
@@ -126,15 +74,19 @@ func (param *funcParamInfo) Name() string {
} }
func (param *funcParamInfo) Type() string { func (param *funcParamInfo) Type() string {
return "any" return TypeAny
}
func (param *funcParamInfo) IsDefault() bool {
return (param.flags & PfDefault) != 0
} }
func (param *funcParamInfo) IsOptional() bool { func (param *funcParamInfo) IsOptional() bool {
return (param.flags & pfOptional) != 0 return (param.flags & PfOptional) != 0
} }
func (param *funcParamInfo) IsRepeat() bool { func (param *funcParamInfo) IsRepeat() bool {
return (param.flags & pfRepeat) != 0 return (param.flags & PfRepeat) != 0
} }
func (param *funcParamInfo) DefaultValue() any { func (param *funcParamInfo) DefaultValue() any {
@@ -159,7 +111,7 @@ func newFuncInfo(name string, functor Functor, returnType string, params []ExprF
if maxArgs == -1 { if maxArgs == -1 {
return nil, fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name()) return nil, fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name())
} }
if p.IsOptional() { if p.IsDefault() || p.IsOptional() {
maxArgs++ maxArgs++
} else if maxArgs == minArgs { } else if maxArgs == minArgs {
minArgs++ minArgs++
@@ -168,6 +120,7 @@ func newFuncInfo(name string, functor Functor, returnType string, params []ExprF
return nil, fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name()) return nil, fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name())
} }
if p.IsRepeat() { if p.IsRepeat() {
minArgs--
maxArgs = -1 maxArgs = -1
} }
} }
@@ -206,7 +159,7 @@ func (info *funcInfo) ToString(opt FmtOpt) string {
} }
sb.WriteString(p.Name()) sb.WriteString(p.Name())
if p.IsOptional() { if p.IsDefault() {
sb.WriteByte('=') sb.WriteByte('=')
if s, ok := p.DefaultValue().(string); ok { if s, ok := p.DefaultValue().(string); ok {
sb.WriteByte('"') sb.WriteByte('"')
@@ -221,13 +174,13 @@ func (info *funcInfo) ToString(opt FmtOpt) string {
if info.maxArgs < 0 { if info.maxArgs < 0 {
sb.WriteString(" ...") sb.WriteString(" ...")
} }
sb.WriteString("): ") sb.WriteString("):")
if len(info.returnType) > 0 { if len(info.returnType) > 0 {
sb.WriteString(info.returnType) sb.WriteString(info.returnType)
} else { } else {
sb.WriteString(typeAny) sb.WriteString(TypeAny)
} }
sb.WriteString(" {<body>}") sb.WriteString(" {}")
return sb.String() return sb.String()
} }
+93 -5
View File
@@ -4,13 +4,16 @@
// global-context.go // global-context.go
package expr package expr
import "path/filepath" import (
"path/filepath"
"strings"
)
var globalCtx *SimpleStore var globalCtx *SimpleStore
func ImportInContext(name string) (exists bool) { func ImportInContext(name string) (exists bool) {
var mod *module var mod *builtinModule
if mod, exists = moduleRegister[name]; exists { if mod, exists = builtinModuleRegister[name]; exists {
mod.importFunc(globalCtx) mod.importFunc(globalCtx)
mod.imported = true mod.imported = true
} }
@@ -19,7 +22,7 @@ func ImportInContext(name string) (exists bool) {
func ImportInContextByGlobPattern(pattern string) (count int, err error) { func ImportInContextByGlobPattern(pattern string) (count int, err error) {
var matched bool var matched bool
for name, mod := range moduleRegister { for name, mod := range builtinModuleRegister {
if matched, err = filepath.Match(pattern, name); err == nil { if matched, err = filepath.Match(pattern, name); err == nil {
if matched { if matched {
count++ count++
@@ -40,16 +43,101 @@ func GetVar(ctx ExprContext, name string) (value any, exists bool) {
return return
} }
func GetLocalFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool) {
var v any
if len(name) > 0 {
if v, exists = ctx.GetVar(name); exists && isFunctor(v) {
f, _ := v.(Functor)
item = f.GetFunc()
} else {
item, exists = ctx.GetFuncInfo(name)
}
}
return
}
func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool, ownerCtx ExprContext) { func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool, ownerCtx ExprContext) {
if item, exists = ctx.GetFuncInfo(name); exists { if len(name) > 0 {
if item, exists = GetLocalFuncInfo(ctx, name); exists {
ownerCtx = ctx ownerCtx = ctx
} else if item, exists = globalCtx.GetFuncInfo(name); exists { } else if item, exists = globalCtx.GetFuncInfo(name); exists {
ownerCtx = globalCtx ownerCtx = globalCtx
} }
}
return
}
func GlobalCtrlSet(name string, newValue any) (currentValue any) {
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
currentValue, _ = globalCtx.GetVar(name)
globalCtx.SetVar(name, newValue)
return currentValue
}
func GlobalCtrlGet(name string) (currentValue any) {
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
currentValue, _ = globalCtx.GetVar(name)
return currentValue
}
func CtrlEnable(ctx ExprContext, name string) (currentStatus bool) {
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
if v, exists := ctx.GetVar(name); exists && IsBool(v) {
currentStatus, _ = v.(bool)
}
ctx.SetVar(name, true)
return currentStatus
}
func CtrlDisable(ctx ExprContext, name string) (currentStatus bool) {
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
if v, exists := ctx.GetVar(name); exists && IsBool(v) {
currentStatus, _ = v.(bool)
}
ctx.SetVar(name, false)
return currentStatus
}
func CtrlIsEnabled(ctx ExprContext, name string) (status bool) {
var v any
var exists bool
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
if v, exists = ctx.GetVar(name); !exists {
v, exists = globalCtx.GetVar(name)
}
if exists {
if b, ok := v.(bool); ok {
status = b
}
}
return
}
func getControlString(name string) (s string, exists bool) {
var v any
if v, exists = globalCtx.GetVar(name); exists {
s, exists = v.(string)
}
return return
} }
func init() { func init() {
globalCtx = NewSimpleStore() globalCtx = NewSimpleStore()
initDefaultVars(globalCtx)
ImportBuiltinsFuncs(globalCtx) ImportBuiltinsFuncs(globalCtx)
} }
+6 -6
View File
@@ -16,10 +16,10 @@ func EvalString(ctx ExprContext, source string) (result any, err error) {
r := strings.NewReader(source) r := strings.NewReader(source)
scanner := NewScanner(r, DefaultTranslations()) scanner := NewScanner(r, DefaultTranslations())
parser := NewParser(ctx) parser := NewParser()
if tree, err = parser.Parse(scanner); err == nil { if tree, err = parser.Parse(scanner); err == nil {
result, err = tree.eval(ctx, true) result, err = tree.Eval(ctx)
} }
return return
} }
@@ -38,10 +38,10 @@ func EvalStringV(source string, args []Arg) (result any, err error) {
for _, arg := range args { for _, arg := range args {
if isFunc(arg.Value) { if isFunc(arg.Value) {
if f, ok := arg.Value.(FuncTemplate); ok { if f, ok := arg.Value.(FuncTemplate); ok {
functor := newGolangFunctor(f) functor := NewGolangFunctor(f)
// ctx.RegisterFunc(arg.Name, functor, 0, -1) // ctx.RegisterFunc(arg.Name, functor, 0, -1)
ctx.RegisterFunc(arg.Name, functor, typeAny, []ExprFuncParam{ ctx.RegisterFunc(arg.Name, functor, TypeAny, []ExprFuncParam{
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 0), NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, 0),
}) })
} else { } else {
err = fmt.Errorf("invalid function specification: %q", arg.Name) err = fmt.Errorf("invalid function specification: %q", arg.Name)
@@ -68,7 +68,7 @@ func EvalStringV(source string, args []Arg) (result any, err error) {
func EvalStream(ctx ExprContext, r io.Reader) (result any, err error) { func EvalStream(ctx ExprContext, r io.Reader) (result any, err error) {
var tree *ast var tree *ast
scanner := NewScanner(r, DefaultTranslations()) scanner := NewScanner(r, DefaultTranslations())
parser := NewParser(ctx) parser := NewParser()
if tree, err = parser.Parse(scanner); err == nil { if tree, err = parser.Parse(scanner); err == nil {
result, err = tree.Eval(ctx) result, err = tree.Eval(ctx)
+104
View File
@@ -0,0 +1,104 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// import-utils.go
package expr
import (
"errors"
"fmt"
"os"
"path"
"path/filepath"
"strings"
)
const (
ENV_EXPR_SOURCE_PATH = "EXPR_PATH"
ENV_EXPR_PLUGIN_PATH = "EXPR_PLUGIN_PATH"
)
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(IsString(paramValue) /*|| isList(paramValue)*/) {
err = fmt.Errorf("%s(): param nr %d has wrong type %s, string expected", funcName, paramPos+1, TypeName(paramValue))
}
return
}
func addSourceEnvImportDirs(varName string, dirList []string) []string {
return addEnvImportDirs(ENV_EXPR_SOURCE_PATH, dirList)
}
func addPluginEnvImportDirs(varName string, dirList []string) []string {
return addEnvImportDirs(ENV_EXPR_PLUGIN_PATH, dirList)
}
func addEnvImportDirs(envVarName string, dirList []string) []string {
if dirSpec, exists := os.LookupEnv(envVarName); exists {
dirs := strings.Split(dirSpec, ":")
if dirList == nil {
dirList = dirs
} else {
dirList = append(dirList, dirs...)
}
}
return dirList
}
func addSearchDirs(endingPath string, dirList []string) []string {
if dirSpec, exists := getControlString(ControlSearchPath); exists {
dirs := strings.Split(dirSpec, ":")
if dirList == nil {
dirList = dirs
} else {
if len(endingPath) > 0 {
for _, d := range dirs {
dirList = append(dirList, path.Join(d, endingPath))
}
} else {
dirList = append(dirList, dirs...)
}
}
}
return dirList
}
func buildSearchDirList(endingPath, envVarName string) (dirList []string) {
dirList = addEnvImportDirs(envVarName, dirList)
dirList = addSearchDirs(endingPath, dirList)
return
}
func isFile(filePath string) bool {
info, err := os.Stat(filePath)
return (err == nil || errors.Is(err, os.ErrExist)) && info.Mode().IsRegular()
}
func searchAmongPath(filename string, dirList []string) (filePath string) {
for _, dir := range dirList {
if fullPath := path.Join(dir, filename); isFile(fullPath) {
filePath = fullPath
break
}
}
return
}
func isPathRelative(filePath string) bool {
unixPath := filepath.ToSlash(filePath)
return strings.HasPrefix(unixPath, "./") || strings.HasPrefix(unixPath, "../")
}
func makeFilepath(filename string, dirList []string) (filePath string, err error) {
if path.IsAbs(filename) || isPathRelative(filename) {
if isFile(filename) {
filePath = filename
}
} else {
filePath = searchAmongPath(filename, dirList)
}
if len(filePath) == 0 {
err = fmt.Errorf("file %q not found", filename)
}
return
}
View File
+13 -4
View File
@@ -26,21 +26,21 @@ func NewListIterator(list *ListType, args []any) (it *ListIterator) {
} }
it = &ListIterator{a: list, count: 0, index: -1, start: 0, stop: listLen - 1, step: 1} it = &ListIterator{a: list, count: 0, index: -1, start: 0, stop: listLen - 1, step: 1}
if argc >= 1 { if argc >= 1 {
if i, err := toInt(args[0], "start index"); err == nil { if i, err := ToInt(args[0], "start index"); err == nil {
if i < 0 { if i < 0 {
i = listLen + i i = listLen + i
} }
it.start = i it.start = i
} }
if argc >= 2 { if argc >= 2 {
if i, err := toInt(args[1], "stop index"); err == nil { if i, err := ToInt(args[1], "stop index"); err == nil {
if i < 0 { if i < 0 {
i = listLen + i i = listLen + i
} }
it.stop = i it.stop = i
} }
if argc >= 3 { if argc >= 3 {
if i, err := toInt(args[2], "step"); err == nil { if i, err := ToInt(args[2], "step"); err == nil {
if i < 0 { if i < 0 {
i = -i i = -i
} }
@@ -106,11 +106,20 @@ func (it *ListIterator) CallOperation(name string, args []any) (v any, err error
func (it *ListIterator) Current() (item any, err error) { func (it *ListIterator) Current() (item any, err error) {
a := *(it.a) a := *(it.a)
if it.index >= 0 && it.index <= it.stop { if it.start <= it.stop {
if it.stop < len(a) && it.index >= it.start && it.index <= it.stop {
item = a[it.index] item = a[it.index]
} else { } else {
err = io.EOF err = io.EOF
} }
} else {
if it.start < len(a) && it.index >= it.stop && it.index <= it.start {
item = a[it.index]
} else {
err = io.EOF
}
}
return return
} }
+1 -1
View File
@@ -34,7 +34,7 @@ type ExtIterator interface {
} }
func errNoOperation(name string) error { func errNoOperation(name string) error {
return fmt.Errorf("no %q function defined in the data-source", name) return fmt.Errorf("no %s() function defined in the data-source", name)
} }
func errInvalidDataSource() error { func errInvalidDataSource() error {
+51 -6
View File
@@ -13,10 +13,17 @@ import (
type ListType []any type ListType []any
func newListA(listAny ...any) (list *ListType) { func newListA(listAny ...any) (list *ListType) {
if listAny == nil {
listAny = []any{}
}
return newList(listAny) return newList(listAny)
} }
func newList(listAny []any) (list *ListType) { func newList(listAny []any) (list *ListType) {
return NewList(listAny)
}
func NewList(listAny []any) (list *ListType) {
if listAny != nil { if listAny != nil {
ls := make(ListType, len(listAny)) ls := make(ListType, len(listAny))
for i, item := range listAny { for i, item := range listAny {
@@ -27,17 +34,42 @@ func newList(listAny []any) (list *ListType) {
return return
} }
func MakeList(length, capacity int) (list *ListType) {
if capacity < length {
capacity = length
}
ls := make(ListType, length, capacity)
list = &ls
return
}
func ListFromStrings(stringList []string) (list *ListType) {
list = MakeList(len(stringList), 0)
for i, s := range stringList {
(*list)[i] = s
}
return
}
func (ls *ListType) ToString(opt FmtOpt) (s string) { func (ls *ListType) ToString(opt FmtOpt) (s string) {
indent := GetFormatIndent(opt)
flags := GetFormatFlags(opt)
var sb strings.Builder var sb strings.Builder
sb.WriteByte('[') sb.WriteByte('[')
if len(*ls) > 0 { if len(*ls) > 0 {
if opt&MultiLine != 0 { innerOpt := MakeFormatOptions(flags, indent+1)
sb.WriteString("\n ") nest := strings.Repeat(" ", indent+1)
if flags&MultiLine != 0 {
sb.WriteByte('\n')
sb.WriteString(nest)
} }
for i, item := range []any(*ls) { for i, item := range []any(*ls) {
if i > 0 { if i > 0 {
if opt&MultiLine != 0 { if flags&MultiLine != 0 {
sb.WriteString(",\n ") sb.WriteString(",\n")
sb.WriteString(nest)
} else { } else {
sb.WriteString(", ") sb.WriteString(", ")
} }
@@ -46,17 +78,20 @@ func (ls *ListType) ToString(opt FmtOpt) (s string) {
sb.WriteByte('"') sb.WriteByte('"')
sb.WriteString(s) sb.WriteString(s)
sb.WriteByte('"') sb.WriteByte('"')
} else if formatter, ok := item.(Formatter); ok {
sb.WriteString(formatter.ToString(innerOpt))
} else { } else {
sb.WriteString(fmt.Sprintf("%v", item)) sb.WriteString(fmt.Sprintf("%v", item))
} }
} }
if opt&MultiLine != 0 { if flags&MultiLine != 0 {
sb.WriteByte('\n') sb.WriteByte('\n')
sb.WriteString(strings.Repeat(" ", indent))
} }
} }
sb.WriteByte(']') sb.WriteByte(']')
s = sb.String() s = sb.String()
if opt&Truncate != 0 && len(s) > TruncateSize { if flags&Truncate != 0 && len(s) > TruncateSize {
s = TruncateString(s) s = TruncateString(s)
} }
return return
@@ -147,3 +182,13 @@ func deepSame(a, b any, deepCmp deepFuncTemplate) (eq bool, err error) {
return return
} }
func (list *ListType) setItem(index int64, value any) (err error) {
if index >= 0 && index < int64(len(*list)) {
(*list)[index] = value
} else {
err = fmt.Errorf("index %d out of bounds (0, %d)", index, len(*list)-1)
}
return
}
-48
View File
@@ -1,48 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// module-register.go
package expr
import (
"fmt"
)
type module struct {
importFunc func(ExprContext)
description string
imported bool
}
func newModule(importFunc func(ExprContext), description string) *module {
return &module{importFunc, description, false}
}
var moduleRegister map[string]*module
func registerImport(name string, importFunc func(ExprContext), description string) {
if moduleRegister == nil {
moduleRegister = make(map[string]*module)
}
if _, exists := moduleRegister[name]; exists {
panic(fmt.Errorf("module %q already registered", name))
}
moduleRegister[name] = newModule(importFunc, description)
}
func IterateModules(op func(name, description string, imported bool) bool) {
if op != nil {
for name, mod := range moduleRegister {
if !op(name, mod.description, mod.imported) {
break
}
}
}
}
// ----
func init() {
if moduleRegister == nil {
moduleRegister = make(map[string]*module)
}
}
-118
View File
@@ -4,124 +4,6 @@
// operand-dict.go // operand-dict.go
package expr package expr
import (
"fmt"
"reflect"
"strings"
)
type DictType map[any]any
func newDict(dictAny map[any]*term) (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 (dict *DictType) toMultiLine(sb *strings.Builder, indent int) {
sb.WriteString(strings.Repeat("\t", indent))
sb.WriteString("{\n")
first := true
for name, value := range *dict {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
sb.WriteString(strings.Repeat("\t", indent+1))
if key, ok := name.(string); ok {
sb.WriteString(string('"') + key + string('"'))
} else {
sb.WriteString(fmt.Sprintf("%v", name))
}
sb.WriteString(": ")
if f, ok := value.(Formatter); ok {
sb.WriteString(f.ToString(MultiLine))
} else if _, ok = value.(Functor); ok {
sb.WriteString("func(){}")
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteString(strings.Repeat("\t", indent))
sb.WriteString("\n}")
}
func (dict *DictType) ToString(opt FmtOpt) string {
var sb strings.Builder
if opt&MultiLine != 0 {
dict.toMultiLine(&sb, 0)
} else {
sb.WriteByte('{')
first := true
for key, value := range *dict {
if first {
first = false
} else {
sb.WriteString(", ")
}
if s, ok := key.(string); ok {
sb.WriteString(string('"') + s + string('"'))
} else {
sb.WriteString(fmt.Sprintf("%v", key))
}
sb.WriteString(": ")
if formatter, ok := value.(Formatter); ok {
sb.WriteString(formatter.ToString(opt))
} else if t, ok := value.(*term); ok {
sb.WriteString(t.String())
} else {
sb.WriteString(fmt.Sprintf("%#v", value))
}
}
sb.WriteByte('}')
}
return sb.String()
}
func (dict *DictType) String() string {
return dict.ToString(0)
}
func (dict *DictType) TypeName() string {
return "dict"
}
func (dict *DictType) hasKey(target any) (ok bool) {
for key := range *dict {
if ok = reflect.DeepEqual(key, target); ok {
break
}
}
return
}
func (dict *DictType) clone() (c *DictType) {
c = newDict(nil)
for k, v := range *dict {
(*c)[k] = v
}
return
}
func (dict *DictType) merge(second *DictType) {
if second != nil {
for k, v := range *second {
(*dict)[k] = v
}
}
}
// -------- dict term // -------- dict term
func newDictTerm(args map[any]*term) *term { func newDictTerm(args map[any]*term) *term {
+6 -21
View File
@@ -26,21 +26,20 @@ func checkFunctionCall(ctx ExprContext, name string, varParams *[]any) (err erro
if info, exists, owner := GetFuncInfo(ctx, name); exists { if info, exists, owner := GetFuncInfo(ctx, name); exists {
passedCount := len(*varParams) passedCount := len(*varParams)
if info.MinArgs() > passedCount { if info.MinArgs() > passedCount {
err = errTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount) err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
} }
for i, p := range info.Params() { for i, p := range info.Params() {
if i >= passedCount { if i >= passedCount {
if !p.IsOptional() { if !p.IsDefault() {
break break
} }
*varParams = append(*varParams, p.DefaultValue()) *varParams = append(*varParams, p.DefaultValue())
} }
} }
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(*varParams) { if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(*varParams) {
err = errTooMuchParams(name, 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.RegisterFuncInfo(info) ctx.RegisterFuncInfo(info)
} }
} else { } else {
@@ -51,8 +50,8 @@ func checkFunctionCall(ctx ExprContext, name string, varParams *[]any) (err erro
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) { func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
ctx := cloneContext(parentCtx) ctx := cloneContext(parentCtx)
ctx.SetParent(parentCtx)
name, _ := self.tk.Value.(string) name, _ := self.tk.Value.(string)
// fmt.Printf("Call %s(), context: %p\n", name, ctx)
params := make([]any, len(self.children), len(self.children)+5) 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
@@ -85,20 +84,6 @@ func newFuncDefTerm(tk *Token, args []*term) *term {
} }
// -------- eval func def // -------- eval func def
// func _evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
// bodySpec := self.value()
// if expr, ok := bodySpec.(*ast); ok {
// paramList := make([]string, 0, len(self.children))
// for _, param := range self.children {
// paramList = append(paramList, param.source())
// }
// v = newExprFunctor(expr, paramList, ctx)
// } else {
// err = errors.New("invalid function definition: the body specification must be an expression")
// }
// 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 {
@@ -107,12 +92,12 @@ func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
var defValue any var defValue any
flags := paramFlags(0) flags := paramFlags(0)
if len(param.children) > 0 { if len(param.children) > 0 {
flags |= pfOptional flags |= PfDefault
if defValue, err = param.children[0].compute(ctx); err != nil { if defValue, err = param.children[0].compute(ctx); err != nil {
return return
} }
} }
info := newFuncParamFlagDef(param.source(), flags, defValue) info := NewFuncParamFlagDef(param.source(), flags, defValue)
paramList = append(paramList, info) paramList = append(paramList, info)
} }
v = newExprFunctor(expr, paramList, ctx) v = newExprFunctor(expr, paramList, ctx)
-17
View File
@@ -148,23 +148,6 @@ func evalIterator(ctx ExprContext, self *term) (v any, err error) {
return return
} }
// func evalChildren(ctx ExprContext, terms []*term, firstChildValue any) (list *ListType, err error) {
// items := make(ListType, len(terms))
// for i, tree := range terms {
// var param any
// if i == 0 && firstChildValue != nil {
// param = firstChildValue
// } else if param, err = tree.compute(ctx); err != nil {
// break
// }
// items[i] = param
// }
// if err == nil {
// list = &items
// }
// return
// }
func evalSibling(ctx ExprContext, terms []*term, firstChildValue any) (list []any, err error) { func evalSibling(ctx ExprContext, terms []*term, firstChildValue any) (list []any, err error) {
items := make([]any, 0, len(terms)) items := make([]any, 0, len(terms))
for i, tree := range terms { for i, tree := range terms {
+54 -9
View File
@@ -16,14 +16,59 @@ func newAssignTerm(tk *Token) (inst *term) {
} }
} }
func assignCollectionItem(ctx ExprContext, collectionTerm, keyListTerm *term, value any) (err error) {
var collectionValue, keyListValue, keyValue any
var keyList *ListType
var ok bool
if collectionValue, err = collectionTerm.compute(ctx); err != nil {
return
}
if keyListValue, err = keyListTerm.compute(ctx); err != nil {
return
} else if keyList, ok = keyListValue.(*ListType); !ok || len(*keyList) != 1 {
err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, TypeName(keyListValue))
return
}
if keyValue = (*keyList)[0]; keyValue == nil {
err = keyListTerm.Errorf("index/key is nil")
return
}
switch collection := collectionValue.(type) {
case *ListType:
if index, ok := keyValue.(int64); ok {
err = collection.setItem(index, value)
} else {
err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, TypeName(keyValue))
}
case *DictType:
err = collection.setItem(keyValue, value)
default:
err = collectionTerm.Errorf("collection expected")
}
return
}
func assignValue(ctx ExprContext, leftTerm *term, v any) (err error) {
if leftTerm.symbol() == SymIndex {
err = assignCollectionItem(ctx, leftTerm.children[0], leftTerm.children[1], v)
} else {
ctx.UnsafeSetVar(leftTerm.source(), v)
}
return
}
func evalAssign(ctx ExprContext, self *term) (v any, err error) { func evalAssign(ctx ExprContext, self *term) (v any, err error) {
if err = self.checkOperands(); err != nil { if err = self.checkOperands(); err != nil {
return return
} }
leftTerm := self.children[0] leftTerm := self.children[0]
if leftTerm.tk.Sym != SymVariable { leftSym := leftTerm.symbol()
err = leftTerm.tk.Errorf("left operand of %q must be a variable", self.tk.source) if leftSym != SymVariable && leftSym != SymIndex {
err = leftTerm.tk.Errorf("left operand of %q must be a variable or a collection's item", self.tk.source)
return return
} }
@@ -31,22 +76,22 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
if v, err = rightChild.compute(ctx); err == nil { if v, err = rightChild.compute(ctx); err == nil {
if functor, ok := v.(Functor); ok { if functor, ok := v.(Functor); ok {
funcName := rightChild.source() if info := functor.GetFunc(); info != nil {
if info, exists, _ := GetFuncInfo(ctx, funcName); exists {
// 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, func(p ExprFuncParam) ExprFuncParam { return p }) paramSpecs := ForAll(funcDef.params, func(p ExprFuncParam) ExprFuncParam { return p })
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()", rightChild.source())
} }
} else { } else {
ctx.UnsafeSetVar(leftTerm.source(), v) err = assignValue(ctx, leftTerm, v)
} }
} }
if err != nil {
v = nil
}
return return
} }
+11 -11
View File
@@ -25,7 +25,7 @@ func evalNot(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if b, ok := toBool(rightValue); ok { if b, ok := ToBool(rightValue); ok {
v = !b v = !b
} else { } else {
err = self.errIncompatibleType(rightValue) err = self.errIncompatibleType(rightValue)
@@ -48,7 +48,7 @@ func newAndTerm(tk *Token) (inst *term) {
} }
func evalAnd(ctx ExprContext, self *term) (v any, err error) { func evalAnd(ctx ExprContext, self *term) (v any, err error) {
if isEnabled(ctx, ControlBoolShortcut) { if CtrlIsEnabled(ctx, ControlBoolShortcut) {
v, err = evalAndWithShortcut(ctx, self) v, err = evalAndWithShortcut(ctx, self)
} else { } else {
v, err = evalAndWithoutShortcut(ctx, self) v, err = evalAndWithoutShortcut(ctx, self)
@@ -65,8 +65,8 @@ func evalAndWithoutShortcut(ctx ExprContext, self *term) (v any, err error) {
return return
} }
leftBool, lok = toBool(leftValue) leftBool, lok = ToBool(leftValue)
rightBool, rok = toBool(rightValue) rightBool, rok = ToBool(rightValue)
if lok && rok { if lok && rok {
v = leftBool && rightBool v = leftBool && rightBool
@@ -87,13 +87,13 @@ func evalAndWithShortcut(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if leftBool, lok := toBool(leftValue); !lok { if leftBool, lok := ToBool(leftValue); !lok {
err = fmt.Errorf("got %T as left operand type of 'and' operator, it must be bool", leftBool) err = fmt.Errorf("got %T as left operand type of 'and' operator, it must be bool", leftBool)
return return
} else if !leftBool { } else if !leftBool {
v = false v = false
} else if rightValue, err = self.children[1].compute(ctx); err == nil { } else if rightValue, err = self.children[1].compute(ctx); err == nil {
if rightBool, rok := toBool(rightValue); rok { if rightBool, rok := ToBool(rightValue); rok {
v = rightBool v = rightBool
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
@@ -117,7 +117,7 @@ func newOrTerm(tk *Token) (inst *term) {
} }
func evalOr(ctx ExprContext, self *term) (v any, err error) { func evalOr(ctx ExprContext, self *term) (v any, err error) {
if isEnabled(ctx, ControlBoolShortcut) { if CtrlIsEnabled(ctx, ControlBoolShortcut) {
v, err = evalOrWithShortcut(ctx, self) v, err = evalOrWithShortcut(ctx, self)
} else { } else {
v, err = evalOrWithoutShortcut(ctx, self) v, err = evalOrWithoutShortcut(ctx, self)
@@ -134,8 +134,8 @@ func evalOrWithoutShortcut(ctx ExprContext, self *term) (v any, err error) {
return return
} }
leftBool, lok = toBool(leftValue) leftBool, lok = ToBool(leftValue)
rightBool, rok = toBool(rightValue) rightBool, rok = ToBool(rightValue)
if lok && rok { if lok && rok {
v = leftBool || rightBool v = leftBool || rightBool
@@ -156,13 +156,13 @@ func evalOrWithShortcut(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if leftBool, lok := toBool(leftValue); !lok { if leftBool, lok := ToBool(leftValue); !lok {
err = fmt.Errorf("got %T as left operand type of 'or' operator, it must be bool", leftBool) err = fmt.Errorf("got %T as left operand type of 'or' operator, it must be bool", leftBool)
return return
} else if leftBool { } else if leftBool {
v = true v = true
} else if rightValue, err = self.children[1].compute(ctx); err == nil { } else if rightValue, err = self.children[1].compute(ctx); err == nil {
if rightBool, rok := toBool(rightValue); rok { if rightBool, rok := ToBool(rightValue); rok {
v = rightBool v = rightBool
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
+2 -2
View File
@@ -37,11 +37,11 @@ func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
if ImportInContext(module) { if ImportInContext(module) {
count++ count++
} else { } else {
err = self.Errorf("unknown module %q", module) err = self.Errorf("unknown builtin module %q", module)
break break
} }
} else { } else {
err = self.Errorf("expected string at item nr %d, got %T", it.Index()+1, moduleSpec) err = self.Errorf("expected string at item nr %d, got %s", it.Index()+1, TypeName(moduleSpec))
break break
} }
} }
+2 -2
View File
@@ -67,8 +67,8 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
} else if rightValue, err = self.children[1].compute(ctx); err == nil { } else if rightValue, err = self.children[1].compute(ctx); err == nil {
if functor, ok := rightValue.(Functor); ok { if functor, ok := rightValue.(Functor); ok {
//ctx.RegisterFunc(leftTerm.source(), functor, 0, -1) //ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
ctx.RegisterFunc(leftTerm.source(), functor, typeAny, []ExprFuncParam{ ctx.RegisterFunc(leftTerm.source(), functor, TypeAny, []ExprFuncParam{
newFuncParamFlag(paramValue, pfOptional|pfRepeat), NewFuncParamFlag(ParamValue, PfDefault|PfRepeat),
}) })
} else { } else {
v = rightValue v = rightValue
+5 -2
View File
@@ -31,10 +31,13 @@ func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
} }
if sourceCtx != nil { if sourceCtx != nil {
if formatter, ok := sourceCtx.(Formatter); ok { if formatter, ok := sourceCtx.(DictFormat); ok {
v = formatter.ToDict()
} else if formatter, ok := sourceCtx.(Formatter); ok {
v = formatter.ToString(0) v = formatter.ToString(0)
} else { } else {
keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' }) // keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
keys := sourceCtx.EnumVars(nil)
d := make(map[string]any) d := make(map[string]any)
for _, key := range keys { for _, key := range keys {
d[key], _ = sourceCtx.GetVar(key) d[key], _ = sourceCtx.GetVar(key)
+1 -1
View File
@@ -17,7 +17,7 @@ func newExportAllTerm(tk *Token) (inst *term) {
} }
func evalExportAll(ctx ExprContext, self *term) (v any, err error) { func evalExportAll(ctx ExprContext, self *term) (v any, err error) {
enable(ctx, control_export_all) CtrlEnable(ctx, control_export_all)
return return
} }
+1 -1
View File
@@ -23,7 +23,7 @@ func verifyKey(indexTerm *term, indexList *ListType) (index any, err error) {
func verifyIndex(indexTerm *term, indexList *ListType, maxValue int) (index int, err error) { func verifyIndex(indexTerm *term, indexList *ListType, maxValue int) (index int, err error) {
var v int var v int
if v, err = toInt((*indexList)[0], "index expression"); err == nil { if v, err = ToInt((*indexList)[0], "index expression"); err == nil {
if v < 0 && v >= -maxValue { if v < 0 && v >= -maxValue {
v = maxValue + v v = maxValue + v
} }
+1 -1
View File
@@ -36,7 +36,7 @@ func evalLength(ctx ExprContext, self *term) (v any, err error) {
} else if it, ok := childValue.(Iterator); ok { } else if it, ok := childValue.(Iterator); ok {
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) { if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) {
count, _ := extIt.CallOperation(countName, nil) count, _ := extIt.CallOperation(countName, nil)
v, _ = toInt(count, "") v, _ = ToInt(count, "")
} else { } else {
v = int64(it.Index() + 1) v = int64(it.Index() + 1)
} }
+58
View File
@@ -0,0 +1,58 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-plugin.go
package expr
import (
"io"
)
//-------- plugin term
func newPluginTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priSign,
evalFunc: evalPlugin,
}
}
func evalPlugin(ctx ExprContext, self *term) (v any, err error) {
var childValue any
var moduleSpec any
if childValue, err = self.evalPrefix(ctx); err != nil {
return
}
dirList := buildSearchDirList("plugin", ENV_EXPR_PLUGIN_PATH)
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 = self.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)
}
return
}
// init
func init() {
registerTermConstructor(SymKwPlugin, newPluginTerm)
}
+1 -1
View File
@@ -12,7 +12,7 @@ type intPair struct {
} }
func (p *intPair) TypeName() string { func (p *intPair) TypeName() string {
return typePair return TypePair
} }
func (p *intPair) ToString(opt FmtOpt) string { func (p *intPair) ToString(opt FmtOpt) string {
+1 -1
View File
@@ -70,7 +70,7 @@ func newNotEqualTerm(tk *Token) (inst *term) {
func evalNotEqual(ctx ExprContext, self *term) (v any, err error) { func evalNotEqual(ctx ExprContext, self *term) (v any, err error) {
if v, err = evalEqual(ctx, self); err == nil { if v, err = evalEqual(ctx, self); err == nil {
b, _ := toBool(v) b, _ := ToBool(v)
v = !b v = !b
} }
return return
+3 -3
View File
@@ -19,17 +19,17 @@ func newSelectorTerm(tk *Token) (inst *term) {
func trySelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (match bool, selectedValue any, err error) { func trySelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (match bool, selectedValue any, err error) {
caseData, _ := caseSel.(*selectorCase) caseData, _ := caseSel.(*selectorCase)
if caseData.filterList == nil { if caseData.filterList == nil {
selectedValue, err = caseData.caseExpr.eval(ctx, false) selectedValue, err = caseData.caseExpr.Eval(ctx)
match = true match = true
} else if filterList, ok := caseData.filterList.value().([]*term); ok { } else if filterList, ok := caseData.filterList.value().([]*term); ok {
if len(filterList) == 0 && exprValue == int64(caseIndex) { if len(filterList) == 0 && exprValue == int64(caseIndex) {
selectedValue, err = caseData.caseExpr.eval(ctx, false) selectedValue, err = caseData.caseExpr.Eval(ctx)
match = true match = true
} else { } else {
var caseValue any var caseValue any
for _, caseTerm := range filterList { for _, caseTerm := range filterList {
if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue { if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue {
selectedValue, err = caseData.caseExpr.eval(ctx, false) selectedValue, err = caseData.caseExpr.Eval(ctx)
match = true match = true
break break
} }
+12 -16
View File
@@ -6,19 +6,15 @@ package expr
import ( import (
"errors" "errors"
"fmt"
) )
//-------- parser //-------- parser
type parser struct { type parser struct {
ctx ExprContext
} }
func NewParser(ctx ExprContext) (p *parser) { func NewParser() (p *parser) {
p = &parser{ p = &parser{}
ctx: ctx,
}
return p return p
} }
@@ -335,7 +331,7 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
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); tk = scanner.Next() { for tk = scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); tk = scanner.Next() {
if tk.Sym == SymComment { if tk.Sym == SymComment {
continue continue
@@ -404,9 +400,9 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
} }
} }
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.addToken2(tk)
} // }
case SymFuncDef: case SymFuncDef:
var funcDefTerm *term var funcDefTerm *term
if funcDefTerm, err = self.parseFuncDef(scanner); err == nil { if funcDefTerm, err = self.parseFuncDef(scanner); err == nil {
@@ -450,7 +446,7 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
selectorTerm = nil selectorTerm = nil
} }
lastSym = tk.Sym // lastSym = tk.Sym
} }
if err == nil { if err == nil {
err = tk.Error() err = tk.Error()
@@ -458,9 +454,9 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
return return
} }
func checkPrevSymbol(lastSym, wantedSym Symbol, tk *Token) (err error) { // func checkPrevSymbol(lastSym, wantedSym Symbol, tk *Token) (err error) {
if lastSym != wantedSym { // if lastSym != wantedSym {
err = fmt.Errorf(`assign operator (%q) must be preceded by a variable`, tk.source) // err = fmt.Errorf(`assign operator (%q) must be preceded by a variable`, tk.source)
} // }
return // return
} // }
+94
View File
@@ -0,0 +1,94 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// plugin.go
package expr
import (
"fmt"
"plugin"
)
var pluginRegister map[string]*plugin.Plugin
func registerPlugin(name string, p *plugin.Plugin) (err error) {
if pluginExists(name) {
err = fmt.Errorf("plugin %q already loaded", name)
} else {
pluginRegister[name] = p
}
return
}
func pluginExists(name string) (exists bool) {
_, exists = pluginRegister[name]
return
}
func importPlugin( /*ctx ExprContext,*/ dirList []string, name string) (err error) {
var filePath string
var p *plugin.Plugin
var sym plugin.Symbol
var moduleName string
var importFunc func(ExprContext)
var ok bool
decoratedName := fmt.Sprintf("expr-%s-plugin.so", name)
if filePath, err = makeFilepath(decoratedName, dirList); err != nil {
return
}
if p, err = plugin.Open(filePath); err != nil {
return
}
if sym, err = p.Lookup("MODULE_NAME"); err != nil {
return
}
if moduleName = *sym.(*string); moduleName == "" {
err = fmt.Errorf("plugin %q does not provide a valid module name", decoratedName)
return
}
if sym, err = p.Lookup("DEPENDENCIES"); err != nil {
return
}
if deps := *sym.(*[]string); len(deps) > 0 {
// var count int
if err = loadModules(dirList, deps); err != nil {
return
}
}
if sym, err = p.Lookup("ImportFuncs"); err != nil {
return
}
if importFunc, ok = sym.(func(ExprContext)); !ok {
err = fmt.Errorf("plugin %q does not provide a valid import function", decoratedName)
return
}
registerPlugin(moduleName, p)
importFunc(globalCtx)
return
}
func loadModules(dirList []string, moduleNames []string) (err error) {
for _, name := range moduleNames {
if err1 := importPlugin(dirList, name); err1 != nil {
if !ImportInContext(name) {
err = err1
break
}
}
}
return
}
func init() {
pluginRegister = make(map[string]*plugin.Plugin)
}
+8 -3
View File
@@ -178,13 +178,18 @@ func (self *scanner) fetchNextToken() (tk *Token) {
tk = self.makeToken(SymDot, ch) tk = self.makeToken(SymDot, ch)
} }
case '\'': case '\'':
if escape {
tk = self.makeToken(SymQuote, ch) tk = self.makeToken(SymQuote, ch)
escape = false
} else {
tk = self.fetchString(ch)
}
case '"': case '"':
if escape { if escape {
tk = self.makeToken(SymDoubleQuote, ch) tk = self.makeToken(SymDoubleQuote, ch)
escape = false escape = false
} else { } else {
tk = self.fetchString() tk = self.fetchString(ch)
} }
case '`': case '`':
tk = self.makeToken(SymBackTick, ch) tk = self.makeToken(SymBackTick, ch)
@@ -533,7 +538,7 @@ func (self *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk
return return
} }
func (self *scanner) fetchString() (tk *Token) { func (self *scanner) fetchString(termCh byte) (tk *Token) {
var err error var err error
var ch, prev byte var ch, prev byte
var sb strings.Builder var sb strings.Builder
@@ -554,7 +559,7 @@ func (self *scanner) fetchString() (tk *Token) {
sb.WriteByte(ch) sb.WriteByte(ch)
} }
prev = 0 prev = 0
} else if ch == '"' { } else if ch == termCh {
break break
} else { } else {
prev = ch prev = ch
-244
View File
@@ -1,244 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// simple-func-store.go
package expr
import (
"fmt"
"slices"
"strings"
)
type SimpleFuncStore struct {
SimpleVarStore
funcStore map[string]*funcInfo
}
type paramFlags uint16
const (
pfOptional paramFlags = 1 << iota
pfRepeat
)
type funcParamInfo struct {
name string
flags paramFlags
defaultValue any
}
func newFuncParam(name string) *funcParamInfo {
return &funcParamInfo{name: name}
}
func newFuncParamFlag(name string, flags paramFlags) *funcParamInfo {
return &funcParamInfo{name: name, flags: flags}
}
func newFuncParamFlagDef(name string, flags paramFlags, defValue any) *funcParamInfo {
return &funcParamInfo{name: name, flags: flags, defaultValue: defValue}
}
func (param *funcParamInfo) Name() string {
return param.name
}
func (param *funcParamInfo) Type() string {
return "any"
}
func (param *funcParamInfo) IsOptional() bool {
return (param.flags & pfOptional) != 0
}
func (param *funcParamInfo) IsRepeat() bool {
return (param.flags & pfRepeat) != 0
}
func (param *funcParamInfo) DefaultValue() any {
return param.defaultValue
}
type funcInfo struct {
name string
minArgs int
maxArgs int
functor Functor
params []ExprFuncParam
returnType string
}
func (info *funcInfo) Params() []ExprFuncParam {
return info.params
}
func (info *funcInfo) ReturnType() string {
return info.returnType
}
func (info *funcInfo) ToString(opt FmtOpt) string {
var sb strings.Builder
sb.WriteByte('(')
if info.params != nil {
for i, p := range info.params {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(p.Name())
if p.IsOptional() {
sb.WriteByte('=')
if s, ok := p.DefaultValue().(string); ok {
sb.WriteByte('"')
sb.WriteString(s)
sb.WriteByte('"')
} else {
sb.WriteString(fmt.Sprintf("%v", p.DefaultValue()))
}
}
}
}
if info.maxArgs < 0 {
sb.WriteString(" ...")
}
sb.WriteString(") -> ")
if len(info.returnType) > 0 {
sb.WriteString(info.returnType)
} else {
sb.WriteString(typeAny)
}
return sb.String()
}
func (info *funcInfo) Name() string {
return info.name
}
func (info *funcInfo) MinArgs() int {
return info.minArgs
}
func (info *funcInfo) MaxArgs() int {
return info.maxArgs
}
func (info *funcInfo) Functor() Functor {
return info.functor
}
func NewSimpleFuncStore() *SimpleFuncStore {
ctx := &SimpleFuncStore{
SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
funcStore: make(map[string]*funcInfo),
}
//ImportBuiltinsFuncs(ctx)
return ctx
}
func (ctx *SimpleFuncStore) Clone() ExprContext {
svs := ctx.SimpleVarStore
return &SimpleFuncStore{
// SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
SimpleVarStore: SimpleVarStore{varStore: svs.cloneVars()},
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
}
}
func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("funcs: {\n")
first := true
names := ctx.EnumFuncs(func(name string) bool { return true })
slices.Sort(names)
for _, name := range names {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
value, _ := ctx.GetFuncInfo(name)
sb.WriteString(strings.Repeat("\t", indent+1))
sb.WriteString(name)
//sb.WriteString("=")
if formatter, ok := value.(Formatter); ok {
sb.WriteString(formatter.ToString(0))
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteString("\n}\n")
}
func (ctx *SimpleFuncStore) ToString(opt FmtOpt) string {
var sb strings.Builder
sb.WriteString(ctx.SimpleVarStore.ToString(opt))
funcsCtxToBuilder(&sb, ctx, 0)
return sb.String()
}
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
info, exists = ctx.funcStore[name]
return
}
// func (ctx *SimpleFuncStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
// ctx.funcStore[name] = &funcInfo{name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor}
// }
func (ctx *SimpleFuncStore) RegisterFuncInfo(info ExprFunc) {
ctx.funcStore[info.Name()], _ = info.(*funcInfo)
}
func (ctx *SimpleFuncStore) RegisterFunc2(name string, functor Functor, returnType string, params []ExprFuncParam) error {
var minArgs = 0
var maxArgs = 0
if params != nil {
for _, p := range params {
if maxArgs == -1 {
return fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name())
// } else if p.IsRepeat() {
// maxArgs = -1
// continue
}
if p.IsOptional() {
maxArgs++
} else if maxArgs == minArgs {
minArgs++
maxArgs++
} else {
return fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name())
}
if p.IsRepeat() {
maxArgs = -1
}
}
}
ctx.funcStore[name] = &funcInfo{
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, params: params,
}
return nil
}
func (ctx *SimpleFuncStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
funcNames = make([]string, 0)
for name := range ctx.funcStore {
if acceptor != nil {
if acceptor(name) {
funcNames = append(funcNames, name)
}
} else {
funcNames = append(funcNames, name)
}
}
return
}
func (ctx *SimpleFuncStore) Call(name string, args []any) (result any, err error) {
if info, exists := ctx.funcStore[name]; exists {
functor := info.functor
result, err = functor.Invoke(ctx, name, args)
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
+59 -8
View File
@@ -7,10 +7,11 @@ package expr
import ( import (
"fmt" "fmt"
"slices" "slices"
"strings" // "strings"
) )
type SimpleStore struct { type SimpleStore struct {
parent ExprContext
varStore map[string]any varStore map[string]any
funcStore map[string]ExprFunc funcStore map[string]ExprFunc
} }
@@ -26,6 +27,14 @@ func NewSimpleStore() *SimpleStore {
func filterRefName(name string) bool { return name[0] != '@' } func filterRefName(name string) bool { return name[0] != '@' }
func filterPrivName(name string) bool { return name[0] != '_' } func filterPrivName(name string) bool { return name[0] != '_' }
func (ctx *SimpleStore) SetParent(parentCtx ExprContext) {
ctx.parent = parentCtx
}
func (ctx *SimpleStore) GetParent() ExprContext {
return ctx.parent
}
func (ctx *SimpleStore) Clone() ExprContext { func (ctx *SimpleStore) Clone() ExprContext {
return &SimpleStore{ return &SimpleStore{
varStore: CloneFilteredMap(ctx.varStore, filterRefName), varStore: CloneFilteredMap(ctx.varStore, filterRefName),
@@ -41,11 +50,11 @@ func (ctx *SimpleStore) Merge(src ExprContext) {
ctx.funcStore[name], _ = src.GetFuncInfo(name) 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(filterPrivName) { for _, name := range ctx.EnumVars(nil) {
if first { if first {
first = false first = false
} else { } else {
@@ -68,7 +77,7 @@ func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
} }
} }
sb.WriteString(strings.Repeat("\t", indent)) sb.WriteString(strings.Repeat("\t", indent))
sb.WriteString("\n}\n") sb.WriteString("\n}")
} }
func varsCtxToString(ctx ExprContext, indent int) string { func varsCtxToString(ctx ExprContext, indent int) string {
@@ -91,23 +100,65 @@ func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
} }
value, _ := ctx.GetFuncInfo(name) value, _ := ctx.GetFuncInfo(name)
sb.WriteString(strings.Repeat("\t", indent+1)) sb.WriteString(strings.Repeat("\t", indent+1))
sb.WriteString(name)
//sb.WriteString("=")
if formatter, ok := value.(Formatter); ok { if formatter, ok := value.(Formatter); ok {
sb.WriteString(formatter.ToString(0)) sb.WriteString(formatter.ToString(0))
} else { } else {
sb.WriteString(fmt.Sprintf("%v", value)) sb.WriteString(fmt.Sprintf("%v", value))
} }
} }
sb.WriteString("\n}\n") sb.WriteString("\n}")
} }
func (ctx *SimpleStore) ToString(opt FmtOpt) string { func (ctx *SimpleStore) ToString(opt FmtOpt) string {
var sb strings.Builder var sb strings.Builder
varsCtxToBuilder(&sb, ctx, 0) varsCtxToBuilder(&sb, ctx, 0)
sb.WriteByte('\n')
funcsCtxToBuilder(&sb, ctx, 0) funcsCtxToBuilder(&sb, ctx, 0)
return sb.String() return sb.String()
} }
*/
func (ctx *SimpleStore) ToString(opt FmtOpt) string {
dict := ctx.ToDict()
return dict.ToString(opt)
}
func (ctx *SimpleStore) varsToDict(dict *DictType) *DictType {
names := ctx.EnumVars(nil)
slices.Sort(names)
for _, name := range ctx.EnumVars(nil) {
value, _ := ctx.GetVar(name)
if f, ok := value.(Formatter); ok {
(*dict)[name] = f.ToString(0)
} else if _, ok = value.(Functor); ok {
(*dict)[name] = "func(){}"
} else {
(*dict)[name] = fmt.Sprintf("%v", value)
}
}
return dict
}
func (ctx *SimpleStore) funcsToDict(dict *DictType) *DictType {
names := ctx.EnumFuncs(func(name string) bool { return true })
slices.Sort(names)
for _, name := range names {
value, _ := ctx.GetFuncInfo(name)
if formatter, ok := value.(Formatter); ok {
(*dict)[name] = formatter.ToString(0)
} else {
(*dict)[name] = fmt.Sprintf("%v", value)
}
}
return dict
}
func (ctx *SimpleStore) ToDict() (dict *DictType) {
dict = MakeDict()
(*dict)["variables"] = ctx.varsToDict(MakeDict())
(*dict)["functions"] = ctx.funcsToDict(MakeDict())
return
}
func (ctx *SimpleStore) GetVar(varName string) (v any, exists bool) { func (ctx *SimpleStore) GetVar(varName string) (v any, exists bool) {
v, exists = ctx.varStore[varName] v, exists = ctx.varStore[varName]
@@ -174,7 +225,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 := GetLocalFuncInfo(ctx, name); exists {
functor := info.Functor() functor := info.Functor()
result, err = functor.Invoke(ctx, name, args) result, err = functor.Invoke(ctx, name, args)
} else { } else {
-126
View File
@@ -1,126 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// simple-var-store.go
package expr
import (
"fmt"
"strings"
)
type SimpleVarStore struct {
varStore map[string]any
}
func NewSimpleVarStore() *SimpleVarStore {
return &SimpleVarStore{
varStore: make(map[string]any),
}
}
func (ctx *SimpleVarStore) cloneVars() (vars map[string]any) {
return CloneFilteredMap(ctx.varStore, func(name string) bool { return name[0] != '@' })
}
func (ctx *SimpleVarStore) Clone() (clone ExprContext) {
// fmt.Println("*** Cloning context ***")
clone = &SimpleVarStore{
varStore: ctx.cloneVars(),
}
return clone
}
func (ctx *SimpleVarStore) GetVar(varName string) (v any, exists bool) {
v, exists = ctx.varStore[varName]
return
}
func (ctx *SimpleVarStore) UnsafeSetVar(varName string, value any) {
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
ctx.varStore[varName] = value
}
func (ctx *SimpleVarStore) SetVar(varName string, value any) {
// fmt.Printf("[%p] SetVar(%v, %v)\n", ctx, varName, value)
if allowedValue, ok := fromGenericAny(value); ok {
ctx.varStore[varName] = allowedValue
} else {
panic(fmt.Errorf("unsupported type %T of value %v", value, value))
}
}
func (ctx *SimpleVarStore) EnumVars(acceptor func(name string) (accept bool)) (varNames []string) {
varNames = make([]string, 0)
for name := range ctx.varStore {
if acceptor != nil {
if acceptor(name) {
varNames = append(varNames, name)
}
} else {
varNames = append(varNames, name)
}
}
return
}
func (ctx *SimpleVarStore) GetFuncInfo(name string) (f ExprFunc, exists bool) {
return
}
func (ctx *SimpleVarStore) Call(name string, args []any) (result any, err error) {
return
}
// func (ctx *SimpleVarStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
// }
func (ctx *SimpleVarStore) RegisterFuncInfo(info ExprFunc) {
}
func (ctx *SimpleVarStore) RegisterFunc2(name string, f Functor, returnType string, param []ExprFuncParam) error {
return nil
}
func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
return
}
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("vars: {\n")
first := true
for _, name := range ctx.EnumVars(func(name string) bool { return name[0] != '_' }) {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
value, _ := ctx.GetVar(name)
sb.WriteString(strings.Repeat("\t", indent+1))
sb.WriteString(name)
sb.WriteString(": ")
if f, ok := value.(Formatter); ok {
sb.WriteString(f.ToString(0))
} else if _, ok = value.(Functor); ok {
sb.WriteString("func(){}")
// } else if _, ok = value.(map[any]any); ok {
// sb.WriteString("dict{}")
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteString(strings.Repeat("\t", indent))
sb.WriteString("\n}\n")
}
func varsCtxToString(ctx ExprContext, indent int) string {
var sb strings.Builder
varsCtxToBuilder(&sb, ctx, indent)
return sb.String()
}
func (ctx *SimpleVarStore) ToString(opt FmtOpt) string {
var sb strings.Builder
varsCtxToBuilder(&sb, ctx, 0)
return sb.String()
}
+2
View File
@@ -101,6 +101,7 @@ const (
SymKwBut SymKwBut
SymKwFunc SymKwFunc
SymKwBuiltin SymKwBuiltin
SymKwPlugin
SymKwIn SymKwIn
SymKwInclude SymKwInclude
SymKwNil SymKwNil
@@ -113,6 +114,7 @@ func init() {
keywords = map[string]Symbol{ keywords = map[string]Symbol{
"AND": SymKwAnd, "AND": SymKwAnd,
"BUILTIN": SymKwBuiltin, "BUILTIN": SymKwBuiltin,
"PLUGIN": SymKwPlugin,
"BUT": SymKwBut, "BUT": SymKwBut,
"FUNC": SymKwFunc, "FUNC": SymKwFunc,
"IN": SymKwIn, "IN": SymKwIn,
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// t_func-base_test.go // t_builtin-base_test.go
package expr package expr
import ( import (
@@ -10,7 +10,7 @@ import (
) )
func TestFuncBase(t *testing.T) { func TestFuncBase(t *testing.T) {
section := "Func-Base" section := "Builtin-Base"
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`isNil(nil)`, true, nil}, /* 1 */ {`isNil(nil)`, true, nil},
@@ -23,7 +23,7 @@ func TestFuncBase(t *testing.T) {
/* 8 */ {`int("432")`, int64(432), nil}, /* 8 */ {`int("432")`, int64(432), nil},
/* 9 */ {`int("1.5")`, nil, errors.New(`strconv.Atoi: parsing "1.5": invalid syntax`)}, /* 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`)}, /* 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`)}, /* 11 */ {`int(nil)`, nil, errors.New(`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},
/* 14 */ {`isFloat(3.1)`, true, nil}, /* 14 */ {`isFloat(3.1)`, true, nil},
+24
View File
@@ -0,0 +1,24 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_builtin-import_test.go
package expr
import (
"testing"
)
func TestFuncImport(t *testing.T) {
section := "Builtin-Import"
inputs := []inputType{
/* 1 */ {`builtin "import"; import("./test-resources/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-resources/test-funcs.expr"); six()`, int64(6), nil},
/* 4 */ {`builtin "import"; import("./test-resources/sample-export-all.expr"); six()`, int64(6), nil},
}
t.Setenv("EXPR_PATH", "test-resources")
// parserTestSpec(t, section, inputs, 69)
parserTest(t, section, inputs)
}
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// t_func-math-arith_test.go // t_builtin-math-arith_test.go
package expr package expr
import ( import (
@@ -10,7 +10,7 @@ import (
) )
func TestFuncMathArith(t *testing.T) { func TestFuncMathArith(t *testing.T) {
section := "Func-Math-Arith" section := "Builtin-Math-Arith"
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil}, /* 1 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil},
/* 2 */ {`builtin "math.arith"; add(1,2,3)`, int64(6), nil}, /* 2 */ {`builtin "math.arith"; add(1,2,3)`, int64(6), nil},
+29
View File
@@ -0,0 +1,29 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_builtin-os-file_test.go
package expr
import (
"errors"
"testing"
)
func TestFuncOs(t *testing.T) {
section := "Builtin-OS-File"
inputs := []inputType{
/* 1 */ {`builtin "os.file"`, int64(1), nil},
/* 2 */ {`builtin "os.file"; handle=fileOpen("/etc/hosts"); fileClose(handle)`, true, nil},
/* 3 */ {`builtin "os.file"; handle=fileOpen("/etc/hostsX")`, nil, errors.New(`open /etc/hostsX: no such file or directory`)},
/* 4 */ {`builtin "os.file"; handle=fileCreate("/tmp/dummy"); fileClose(handle)`, true, nil},
/* 5 */ {`builtin "os.file"; handle=fileAppend("/tmp/dummy"); fileWrite(handle, "bye-bye"); fileClose(handle)`, true, nil},
/* 6 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); word=fileRead(handle, "-"); fileClose(handle);word`, "bye", nil},
/* 7 */ {`builtin "os.file"; word=fileRead(nil, "-")`, nil, errors.New(`readFileFunc(): invalid file handle`)},
/* 7 */ {`builtin "os.file"; fileWrite(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_builtin-string_test.go
package expr
import (
"errors"
"testing"
)
func TestFuncString(t *testing.T) {
section := "Builtin-String"
inputs := []inputType{
/* 1 */ {`builtin "string"; strJoin("-", "one", "two", "three")`, "one-two-three", nil},
/* 2 */ {`builtin "string"; strJoin("-", ["one", "two", "three"])`, "one-two-three", nil},
/* 3 */ {`builtin "string"; ls= ["one", "two", "three"]; strJoin("-", ls)`, "one-two-three", nil},
/* 4 */ {`builtin "string"; ls= ["one", "two", "three"]; strJoin(1, ls)`, nil, errors.New(`strJoin(): the "separator" parameter must be a string, got a integer (1)`)},
/* 5 */ {`builtin "string"; ls= ["one", 2, "three"]; strJoin("-", ls)`, nil, errors.New(`strJoin(): expected string, got integer (2)`)},
/* 6 */ {`builtin "string"; "<"+strTrim(" bye bye ")+">"`, "<bye bye>", nil},
/* 7 */ {`builtin "string"; strSub("0123456789", 1,2)`, "12", nil},
/* 8 */ {`builtin "string"; strSub("0123456789", -3,2)`, "78", nil},
/* 9 */ {`builtin "string"; strSub("0123456789", -3)`, "789", nil},
/* 10 */ {`builtin "string"; strSub("0123456789")`, "0123456789", nil},
/* 11 */ {`builtin "string"; strStartsWith("0123456789", "xyz", "012")`, true, nil},
/* 12 */ {`builtin "string"; strStartsWith("0123456789", "xyz", "0125")`, false, nil},
/* 13 */ {`builtin "string"; strStartsWith("0123456789")`, nil, errors.New(`strStartsWith(): too few params -- expected 2 or more, got 1`)},
/* 14 */ {`builtin "string"; strEndsWith("0123456789", "xyz", "789")`, true, nil},
/* 15 */ {`builtin "string"; strEndsWith("0123456789", "xyz", "0125")`, false, nil},
/* 16 */ {`builtin "string"; strEndsWith("0123456789")`, nil, errors.New(`strEndsWith(): too few params -- expected 2 or more, got 1`)},
/* 17 */ {`builtin "string"; strSplit("one-two-three", "-")`, newListA("one", "two", "three"), nil},
/* 18 */ {`builtin "string"; strJoin("-", [1, "two", "three"])`, nil, errors.New(`strJoin(): expected string, got integer (1)`)},
/* 19 */ {`builtin "string"; strJoin()`, nil, errors.New(`strJoin(): 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, section, inputs, 19)
parserTest(t, section, inputs)
}
+4 -1
View File
@@ -28,6 +28,9 @@ func TestDictParser(t *testing.T) {
/* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil}, /* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil},
/* 6 */ {`{1:"one"} + {2:"two"}`, map[any]any{1: "one", 2: "two"}, nil}, /* 6 */ {`{1:"one"} + {2:"two"}`, map[any]any{1: "one", 2: "two"}, nil},
/* 7 */ {`2 in {1:"one", 2:"two"}`, true, nil}, /* 7 */ {`2 in {1:"one", 2:"two"}`, true, nil},
/* 8 */ {`D={"a":1, "b":2}; D["a"]=9; D`, map[any]any{"a": 9, "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`)},
} }
succeeded := 0 succeeded := 0
@@ -46,7 +49,7 @@ func TestDictParser(t *testing.T) {
ctx.SetVar("var1", int64(123)) ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc") ctx.SetVar("var2", "abc")
ImportMathFuncs(ctx) ImportMathFuncs(ctx)
parser := NewParser(ctx) parser := NewParser()
logTest(t, i+1, "Dict", input.source, input.wantResult, input.wantErr) logTest(t, i+1, "Dict", input.source, input.wantResult, input.wantErr)
+1 -1
View File
@@ -14,7 +14,7 @@ func TestExpr(t *testing.T) {
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 */ {`builtin "os.file"; f=openFile("test-file.txt"); line=readFile(f); closeFile(f); line`, "uno", nil}, /* 3 */ {`builtin "os.file"; f=fileOpen("test-file.txt"); line=fileRead(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 */ {`
-24
View File
@@ -1,24 +0,0 @@
// 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
@@ -1,29 +0,0 @@
// 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
@@ -1,69 +0,0 @@
// 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)
}
+4 -3
View File
@@ -29,6 +29,7 @@ func TestFuncs(t *testing.T) {
/* 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, errors.New(`[1:16] can't mix default and non-default parameters`)}, /* 16 */ {`f=func(x,n=2,y){x+n}`, nil, errors.New(`[1:16] can't mix default and non-default parameters`)},
/* 17 */ {`f=func(x,n){1}; f(3,4,)`, nil, errors.New(`[1:24] expected "function-param-value", got ")"`)}, /* 17 */ {`f=func(x,n){1}; f(3,4,)`, 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", ".")
@@ -42,8 +43,8 @@ func dummy(ctx ExprContext, name string, args []any) (result any, err error) {
} }
func TestFunctionToStringSimple(t *testing.T) { func TestFunctionToStringSimple(t *testing.T) {
source := newGolangFunctor(dummy) source := NewGolangFunctor(dummy)
want := "func() {<body>}" want := "func() {}"
got := source.ToString(0) got := source.ToString(0)
if got != want { if got != want {
t.Errorf(`(func() -> result = %v [%T], want = %v [%T]`, got, got, want, want) t.Errorf(`(func() -> result = %v [%T], want = %v [%T]`, got, got, want, want)
@@ -51,7 +52,7 @@ 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)
got := source.GetFunc() got := source.GetFunc()
if got != want { if got != want {
+162
View File
@@ -0,0 +1,162 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_iter-list_test.go
package expr
import (
"io"
"testing"
)
func TestNewListIterator(t *testing.T) {
list := newListA("a", "b", "c", "d")
it := NewListIterator(list, []any{1, 3, 1})
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "b" {
t.Errorf("expcted %q, got %q", "b", item)
} else {
t.Logf("Next: %v", item)
}
}
func TestNewListIterator2(t *testing.T) {
list := newListA("a", "b", "c", "d")
it := NewListIterator(list, []any{3, 1, -1})
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "d" {
t.Errorf("expcted %q, got %q", "d", item)
} else {
t.Logf("Next: %v", item)
}
}
func TestNewListIterator3(t *testing.T) {
list := newListA("a", "b", "c", "d")
it := NewListIterator(list, []any{1, -1, 1})
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "b" {
t.Errorf("expcted %q, got %q", "b", item)
} else {
t.Logf("Next: %v", item)
}
}
func TestNewIterList2(t *testing.T) {
list := []any{"a", "b", "c", "d"}
it := NewArrayIterator(list)
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "a" {
t.Errorf("expcted %q, got %q", "a", item)
} else {
t.Logf("Next: %v", item)
}
}
func TestNewIterList3(t *testing.T) {
list := []any{"a", "b", "c", "d"}
it := NewAnyIterator(list)
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "a" {
t.Errorf("expcted %q, got %q", "a", item)
} else {
t.Logf("Next: %v", item)
}
}
func TestNewIterList4(t *testing.T) {
list := any(nil)
it := NewAnyIterator(list)
if _, err := it.Next(); err != io.EOF {
t.Errorf("error: %v", err)
}
}
func TestNewIterList5(t *testing.T) {
list := "123"
it := NewAnyIterator(list)
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "123" {
t.Errorf("expcted %q, got %q", "123", item)
} else {
t.Logf("Next: %v", item)
}
}
func TestNewIterList6(t *testing.T) {
list := newListA("a", "b", "c", "d")
it1 := NewAnyIterator(list)
it := NewAnyIterator(it1)
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "a" {
t.Errorf("expcted %q, got %q", "a", item)
} else {
t.Logf("Next: %v", item)
}
}
func TestNewString(t *testing.T) {
list := "123"
it := NewAnyIterator(list)
if s := it.String(); s != "$(#1)" {
t.Errorf("expected $(#1), got %s", s)
}
}
func TestHasOperation(t *testing.T) {
list := newListA("a", "b", "c", "d")
it := NewListIterator(list, []any{1, 3, 1})
hasOp := it.HasOperation("reset")
if !hasOp {
t.Errorf("HasOperation(reset) must be true, got false")
}
}
func TestCallOperationReset(t *testing.T) {
list := newListA("a", "b", "c", "d")
it := NewListIterator(list, []any{1, 3, 1})
if v, err := it.CallOperation("reset", nil); err != nil {
t.Errorf("Error on CallOperation(reset): %v", err)
} else {
t.Logf("Reset result: %v", v)
}
}
func TestCallOperationIndex(t *testing.T) {
list := newListA("a", "b", "c", "d")
it := NewListIterator(list, []any{1, 3, 1})
if v, err := it.CallOperation("index", nil); err != nil {
t.Errorf("Error on CallOperation(index): %v", err)
} else {
t.Logf("Index result: %v", v)
}
}
func TestCallOperationCount(t *testing.T) {
list := newListA("a", "b", "c", "d")
it := NewListIterator(list, []any{1, 3, 1})
if v, err := it.CallOperation("count", nil); err != nil {
t.Errorf("Error on CallOperation(count): %v", err)
} else {
t.Logf("Count result: %v", v)
}
}
func TestCallOperationUnknown(t *testing.T) {
list := newListA("a", "b", "c", "d")
it := NewListIterator(list, []any{1, 3, 1})
if v, err := it.CallOperation("unknown", nil); err == nil {
t.Errorf("Expected error on CallOperation(unknown), got %v", v)
}
}
+9 -9
View File
@@ -8,15 +8,15 @@ import "testing"
func TestIteratorParser(t *testing.T) { func TestIteratorParser(t *testing.T) {
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`include "iterator.expr"; it=$(ds,3); ()it`, int64(0), nil}, /* 1 */ {`include "test-resources/iterator.expr"; it=$(ds,3); ()it`, int64(0), nil},
/* 2 */ {`include "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 "iterator.expr"; it=$(ds,3); it++; it++; #it`, int64(2), nil}, /* 3 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; #it`, int64(2), nil},
/* 4 */ {`include "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 "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 "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 "file-reader.expr"; it=$(ds,"int.list"); mul(it)`, int64(12000), nil}, /* 7 */ {`builtin "math.arith"; include "test-resources/file-reader.expr"; it=$(ds,"int.list"); mul(it)`, int64(12000), nil},
/* 8 */ {`include "file-reader.expr"; it=$(ds,"int.list"); it++; it.index`, int64(0), nil}, /* 8 */ {`include "test-resources/file-reader.expr"; it=$(ds,"int.list"); it++; it.index`, int64(0), nil},
/* 10 */ {`include "file-reader.expr"; it=$(ds,"int.list"); it.clean`, true, nil}, /* 10 */ {`include "test-resources/file-reader.expr"; it=$(ds,"int.list"); it.clean`, true, nil},
/* 11 */ {`it=$(1,2,3); it++`, int64(1), nil}, /* 11 */ {`it=$(1,2,3); it++`, int64(1), nil},
} }
// inputs1 := []inputType{ // inputs1 := []inputType{
+20 -89
View File
@@ -6,33 +6,26 @@ package expr
import ( import (
"errors" "errors"
"strings"
"testing" "testing"
) )
func TestListParser(t *testing.T) { func TestListParser(t *testing.T) {
section := "List" section := "List"
type inputType struct {
source string
wantResult any
wantErr error
}
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`[]`, []any{}, nil}, /* 1 */ {`[]`, newListA(), nil},
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil}, /* 2 */ {`[1,2,3]`, newListA(int64(1), int64(2), int64(3)), nil},
/* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil}, /* 3 */ {`[1,2,"hello"]`, newListA(int64(1), int64(2), "hello"), nil},
/* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil}, /* 4 */ {`[1+2, not true, "hello"]`, newListA(int64(3), false, "hello"), nil},
/* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil}, /* 5 */ {`[1,2]+[3]`, newListA(int64(1), int64(2), int64(3)), nil},
/* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil}, /* 6 */ {`[1,4,3,2]-[3]`, newListA(int64(1), int64(4), int64(2)), nil},
/* 7 */ {`add([1,4,3,2])`, int64(10), nil}, /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
/* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil}, /* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
/* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil}, /* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
/* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 (2 in 1) has wrong type string, number expected`)}, /* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 (2 in 1) has wrong type string, number expected`)},
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil}, /* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
/* 12 */ {`[1,2,3] << 2+2`, []any{int64(1), int64(2), int64(3), int64(4)}, nil}, /* 12 */ {`[1,2,3] << 2+2`, newListA(int64(1), int64(2), int64(3), int64(4)), nil},
/* 13 */ {`2-1 >> [2,3]`, []any{int64(1), int64(2), int64(3)}, nil}, /* 13 */ {`2-1 >> [2,3]`, newListA(int64(1), int64(2), int64(3)), nil},
/* 14 */ {`[1,2,3][1]`, int64(2), nil}, /* 14 */ {`[1,2,3][1]`, int64(2), nil},
/* 15 */ {`ls=[1,2,3] but ls[1]`, int64(2), nil}, /* 15 */ {`ls=[1,2,3] but ls[1]`, int64(2), nil},
/* 16 */ {`ls=[1,2,3] but ls[-1]`, int64(3), nil}, /* 16 */ {`ls=[1,2,3] but ls[-1]`, int64(3), nil},
@@ -41,89 +34,27 @@ func TestListParser(t *testing.T) {
/* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil}, /* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil},
/* 20 */ {`#["a", "b", "c"]`, int64(3), nil}, /* 20 */ {`#["a", "b", "c"]`, int64(3), nil},
/* 21 */ {`"b" in ["a", "b", "c"]`, true, nil}, /* 21 */ {`"b" in ["a", "b", "c"]`, true, nil},
/* 22 */ {`a=[1,2]; (a)<<3`, []any{1, 2, 3}, nil}, /* 22 */ {`a=[1,2]; (a)<<3`, newListA(int64(1), int64(2), int64(3)), nil},
/* 23 */ {`a=[1,2]; (a)<<3; 1`, []any{1, 2}, nil}, /* 23 */ {`a=[1,2]; (a)<<3; a`, newListA(int64(1), int64(2)), nil},
/* 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][:]`, newListA(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`)}, /* 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 "<<"`)}, /* 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`)}, /* 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 ">>"`)}, /* 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}, /* 31 */ {`a=[1,2]; a<<3`, newListA(int64(1), int64(2), int64(3)), nil},
/* 33 */ {`a=[1,2]; 5>>a`, []any{5, 1, 2}, nil}, /* 33 */ {`a=[1,2]; 5>>a`, newListA(int64(5), int64(1), int64(2)), nil},
/* 34 */ {`L=[1,2]; L[0]=9; L`, newListA(int64(9), int64(2)), nil},
/* 35 */ {`L=[1,2]; L[5]=9; L`, nil, errors.New(`index 5 out of bounds (0, 1)`)},
/* 36 */ {`L=[1,2]; L[]=9; L`, nil, errors.New(`[1:12] index/key specification expected, got [] [list]`)},
/* 37 */ {`L=[1,2]; L[nil]=9;`, nil, errors.New(`[1:12] index/key is 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},
} }
succeeded := 0 // t.Setenv("EXPR_PATH", ".")
failed := 0
// inputs1 := []inputType{ // parserTestSpec(t, section, inputs, 17)
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil}, parserTest(t, section, inputs)
// }
for i, input := range inputs {
var expr *ast
var gotResult any
var gotErr error
ctx := NewSimpleStore()
ImportMathFuncs(ctx)
parser := NewParser(ctx)
logTest(t, i+1, "List", 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 == nil && input.wantResult != nil) || (gotResult != nil && input.wantResult == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotList, okGot := gotResult.([]any); okGot {
if wantList, okWant := input.wantResult.([]any); okWant {
if (gotList == nil && wantList != nil) || (gotList != nil && wantList == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
} else {
equal := len(gotList) == len(wantList)
if equal {
for i, gotItem := range gotList {
wantItem := wantList[i]
equal = gotItem == wantItem
if !equal {
break
}
}
}
if !equal {
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("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
} }
+1 -1
View File
@@ -12,7 +12,7 @@ func TestIterateModules(t *testing.T) {
section := "Module-Register" section := "Module-Register"
mods := make([]string, 0, 100) mods := make([]string, 0, 100)
IterateModules(func(name, description string, imported bool) bool { IterateBuiltinModules(func(name, description string, imported bool) bool {
mods = append(mods, name) mods = append(mods, name)
return true return true
}) })
+8 -6
View File
@@ -18,6 +18,8 @@ type inputType struct {
} }
func TestGeneralParser(t *testing.T) { func TestGeneralParser(t *testing.T) {
section := "Parser"
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`1+/*5*/2`, int64(3), nil}, /* 1 */ {`1+/*5*/2`, int64(3), nil},
/* 2 */ {`3 == 4`, false, nil}, /* 2 */ {`3 == 4`, false, nil},
@@ -115,8 +117,8 @@ func TestGeneralParser(t *testing.T) {
/* 94 */ {`false or true`, true, nil}, /* 94 */ {`false or true`, true, nil},
/* 95 */ {`false or (x==2)`, nil, errors.New(`undefined variable or function "x"`)}, /* 95 */ {`false or (x==2)`, nil, errors.New(`undefined variable or function "x"`)},
/* 96 */ {`a=5; a`, int64(5), nil}, /* 96 */ {`a=5; a`, int64(5), nil},
/* 97 */ {`2=5`, nil, errors.New(`assign operator ("=") must be preceded by a variable`)}, /* 97 */ {`2=5`, nil, errors.New(`[1:2] left operand of "=" must be a variable or a collection's item`)},
/* 98 */ {`2+a=5`, nil, errors.New(`[1:3] left operand of "=" must be a variable`)}, /* 98 */ {`2+a=5`, nil, errors.New(`[1:3] left operand of "=" must be a variable or a collection's item`)},
/* 99 */ {`2+(a=5)`, int64(7), nil}, /* 99 */ {`2+(a=5)`, int64(7), nil},
/* 100 */ {`x ?? "default"`, "default", nil}, /* 100 */ {`x ?? "default"`, "default", nil},
/* 101 */ {`x="hello"; x ?? "default"`, "hello", nil}, /* 101 */ {`x="hello"; x ?? "default"`, "hello", nil},
@@ -145,8 +147,8 @@ func TestGeneralParser(t *testing.T) {
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, "General", inputs, 102) // parserTestSpec(t, section, inputs, 102)
parserTest(t, "General", inputs) parserTest(t, section, inputs)
} }
func parserTestSpec(t *testing.T, section string, inputs []inputType, spec ...int) { func parserTestSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
@@ -186,7 +188,7 @@ func doTest(t *testing.T, section string, input *inputType, count int) (good boo
var gotErr error var gotErr error
ctx := NewSimpleStore() ctx := NewSimpleStore()
parser := NewParser(ctx) parser := NewParser()
logTest(t, count, section, input.source, input.wantResult, input.wantErr) logTest(t, count, section, input.source, input.wantResult, input.wantErr)
@@ -201,7 +203,7 @@ func doTest(t *testing.T, section string, input *inputType, count int) (good boo
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 [%T], want = %v [%T]", count, input.source, gotResult, gotResult, input.wantResult, input.wantResult) t.Errorf("%d: %q -> result = %v [%s], want = %v [%s]", count, input.source, gotResult, TypeName(gotResult), input.wantResult, TypeName(input.wantResult))
good = false good = false
} }
+1 -1
View File
@@ -45,7 +45,7 @@ func TestScanner(t *testing.T) {
/* 22 */ {"@", SymAt, nil, nil}, /* 22 */ {"@", SymAt, nil, nil},
/* 23 */ {`#`, SymHash, nil, nil}, /* 23 */ {`#`, SymHash, nil, nil},
/* 24 */ {`%`, SymPercent, nil, nil}, /* 24 */ {`%`, SymPercent, nil, nil},
/* 25 */ {`'`, SymQuote, nil, nil}, /* 25 */ {`\'`, SymQuote, nil, nil},
/* 26 */ {`\"`, SymDoubleQuote, nil, nil}, /* 26 */ {`\"`, SymDoubleQuote, nil, nil},
/* 27 */ {`_`, SymUndescore, nil, nil}, /* 27 */ {`_`, SymUndescore, nil, nil},
/* 28 */ {`<>`, SymLessGreater, nil, nil}, /* 28 */ {`<>`, SymLessGreater, nil, nil},
+5 -5
View File
@@ -63,7 +63,7 @@ func TestIsString(t *testing.T) {
} }
count++ count++
b, ok := toBool(true) b, ok := ToBool(true)
if !(ok && b) { if !(ok && b) {
t.Errorf("%d: toBool(true) b=%v, ok=%v -> result = false, want true", count, b, ok) t.Errorf("%d: toBool(true) b=%v, ok=%v -> result = false, want true", count, b, ok)
failed++ failed++
@@ -72,7 +72,7 @@ func TestIsString(t *testing.T) {
} }
count++ count++
b, ok = toBool("abc") b, ok = ToBool("abc")
if !(ok && b) { if !(ok && b) {
t.Errorf("%d: toBool(\"abc\") b=%v, ok=%v -> result = false, want true", count, b, ok) t.Errorf("%d: toBool(\"abc\") b=%v, ok=%v -> result = false, want true", count, b, ok)
failed++ failed++
@@ -81,7 +81,7 @@ func TestIsString(t *testing.T) {
} }
count++ count++
b, ok = toBool([]int{}) b, ok = ToBool([]int{})
if ok { if ok {
t.Errorf("%d: toBool([]) b=%v, ok=%v -> result = true, want false", count, b, ok) t.Errorf("%d: toBool([]) b=%v, ok=%v -> result = true, want false", count, b, ok)
failed++ failed++
@@ -97,7 +97,7 @@ func TestToIntOk(t *testing.T) {
wantValue := int(64) wantValue := int(64)
wantErr := error(nil) wantErr := error(nil)
gotValue, gotErr := toInt(source, "test") gotValue, gotErr := ToInt(source, "test")
if gotErr != nil || gotValue != wantValue { if gotErr != nil || gotValue != wantValue {
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v", t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
@@ -110,7 +110,7 @@ func TestToIntErr(t *testing.T) {
wantValue := 0 wantValue := 0
wantErr := errors.New(`test expected integer, got uint64 (64)`) wantErr := errors.New(`test expected integer, got uint64 (64)`)
gotValue, gotErr := toInt(source, "test") gotValue, gotErr := ToInt(source, "test")
if gotErr.Error() != wantErr.Error() || gotValue != wantValue { if gotErr.Error() != wantErr.Error() || gotValue != wantValue {
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v", t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
+5 -5
View File
@@ -156,15 +156,15 @@ func (self *term) toInt(computedValue any, valueDescription string) (i int, err
if index64, ok := computedValue.(int64); ok { if index64, ok := computedValue.(int64); ok {
i = int(index64) i = int(index64)
} else { } else {
err = self.Errorf("%s, got %T (%v)", valueDescription, computedValue, computedValue) err = self.Errorf("%s, got %s (%v)", valueDescription, TypeName(computedValue), computedValue)
} }
return return
} }
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error { func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
leftType := getTypeName(leftValue) leftType := TypeName(leftValue)
leftText := getFormatted(leftValue, Truncate) leftText := getFormatted(leftValue, Truncate)
rightType := getTypeName(rightValue) rightType := TypeName(rightValue)
rightText := getFormatted(rightValue, Truncate) rightText := getFormatted(rightValue, Truncate)
return self.tk.Errorf( return self.tk.Errorf(
"left operand '%s' [%s] and right operand '%s' [%s] are not compatible with operator %q", "left operand '%s' [%s] and right operand '%s' [%s] are not compatible with operator %q",
@@ -175,8 +175,8 @@ func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
func (self *term) errIncompatibleType(value any) error { func (self *term) errIncompatibleType(value any) error {
return self.tk.Errorf( return self.tk.Errorf(
"prefix/postfix operator %q do not support operand '%v' [%T]", "prefix/postfix operator %q do not support operand '%v' [%s]",
self.source(), value, value) self.source(), value, TypeName(value))
} }
func (self *term) Errorf(template string, args ...any) (err error) { func (self *term) Errorf(template string, args ...any) (err error) {
@@ -1,13 +1,13 @@
builtin ["os.file", "base"]; builtin ["os.file", "base"];
readInt=func(fh){ readInt=func(fh){
line=readFile(fh); line=fileRead(fh);
line ? [nil] {nil} :: {int(line)} line ? [nil] {nil} :: {int(line)}
}; };
ds={ ds={
"init":func(filename){ "init":func(filename){
fh=openFile(filename); fh=fileOpen(filename);
fh ? [nil] {nil} :: { @current=readInt(fh); @prev=@current }; fh ? [nil] {nil} :: { @current=readInt(fh); @prev=@current };
fh fh
}, },
@@ -20,7 +20,7 @@ ds={
:: {@prev=current; @current=readInt(fh) but current} :: {@prev=current; @current=readInt(fh) but current}
}, },
"clean":func(fh){ "clean":func(fh){
closeFile(fh) fileClose(fh)
} }
} }
+5 -3
View File
@@ -86,7 +86,7 @@ func numAsFloat(v any) (f float64) {
return return
} }
func toBool(v any) (b bool, ok bool) { func ToBool(v any) (b bool, ok bool) {
ok = true ok = true
switch x := v.(type) { switch x := v.(type) {
case string: case string:
@@ -201,11 +201,13 @@ func CloneFilteredMap[K comparable, V any](source map[K]V, filter func(key K) (a
return CopyFilteredMap(dest, source, filter) return CopyFilteredMap(dest, source, filter)
} }
func toInt(value any, description string) (i int, err error) { func ToInt(value any, description string) (i int, err error) {
if valueInt64, ok := value.(int64); ok { if valueInt64, ok := value.(int64); ok {
i = int(valueInt64) i = int(valueInt64)
} else if valueInt, ok := value.(int); ok {
i = valueInt
} else { } else {
err = fmt.Errorf("%s expected integer, got %T (%v)", description, value, value) err = fmt.Errorf("%s expected integer, got %s (%v)", description, TypeName(value), value)
} }
return return
} }