Compare commits

...

45 Commits

Author SHA1 Message Date
camoroso 531cb1c249 Doc: concepts 2024-06-19 09:38:02 +02:00
camoroso d1b468f35b t_list_test.go: new test cases added 2024-06-19 09:24:50 +02:00
camoroso ff9cf80c66 operator-index.go: ConstLastIndex is checked 2024-06-19 09:24:19 +02:00
camoroso d63b18fd76 parser.go: SymColon resets the firstToken flag; that is needed to specify range indeces that can define negative limits 2024-06-19 09:22:40 +02:00
camoroso 019470faf1 operator-range.go: Fixed priority bug; when range only has the left limit, right limit is set to ConstLastIndex constant 2024-06-19 09:20:02 +02:00
camoroso 302430d57d common-params.go: added the constant ConstLastIndex 2024-06-19 09:17:46 +02:00
camoroso 62ef0d699d token.go: new function IsOneOf() 2024-06-19 09:16:19 +02:00
camoroso 866de759dd file-reader.expr: simpler implementation 2024-06-17 14:07:39 +02:00
camoroso b1d6b6de44 refactored dict's item access 2024-06-17 14:06:33 +02:00
camoroso 7e357eea62 changed to comply new builtin-os-file.go's function names 2024-06-17 09:54:20 +02:00
camoroso d6b4c79736 operator-index.go: dict item access by generic keys implemented 2024-06-17 09:05:23 +02:00
camoroso d066344af8 builtin-os-file.go: changed read and write function names; added fileReadTextAll 2024-06-17 06:59:15 +02:00
camoroso f41dba069e plugin.go: support Liteide and VScode to debug executable file name 2024-06-17 06:57:47 +02:00
camoroso 703ecf6829 Added utility function GetLast() to context 2024-06-17 06:56:32 +02:00
camoroso ba479a1b99 Function call moved from operand-func.go to function.go 2024-06-17 06:54:50 +02:00
camoroso 24e6a293b0 common-params.go: ParamName added 2024-06-12 11:16:57 +02:00
camoroso 28f464c4dc plugins.go: If the main program's file name ends with '.debug', plugins will be loaded from file with name ending with '.debug' 2024-06-12 11:15:31 +02:00
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
74 changed files with 1597 additions and 916 deletions
+1 -8
View File
@@ -10,7 +10,6 @@ import (
type Expr interface {
Eval(ctx ExprContext) (result any, err error)
eval(ctx ExprContext, preset bool) (result any, err error)
String() string
}
@@ -106,16 +105,10 @@ func (self *ast) Finish() {
}
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()
if self.root != nil {
if preset {
initDefaultVars(ctx)
}
// initDefaultVars(ctx)
if self.forest != nil {
for _, root := range self.forest {
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).
// All rights reserved.
// func-builtins.go
// builtin-base.go
package expr
import (
@@ -67,7 +67,7 @@ func boolFunc(ctx ExprContext, name string, args []any) (result any, err error)
case string:
result = len(v) > 0
default:
err = errCantConvert(name, v, "bool")
err = ErrCantConvert(name, v, "bool")
}
return
}
@@ -90,7 +90,7 @@ func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = int64(i)
}
default:
err = errCantConvert(name, v, "int")
err = ErrCantConvert(name, v, "int")
}
return
}
@@ -115,7 +115,7 @@ func decFunc(ctx ExprContext, name string, args []any) (result any, err error) {
case *FractionType:
result = v.toFloat()
default:
err = errCantConvert(name, v, "float")
err = ErrCantConvert(name, v, "float")
}
return
}
@@ -127,9 +127,9 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
if len(args) > 1 {
var ok bool
if den, ok = args[1].(int64); !ok {
err = errExpectedGot(name, "integer", args[1])
err = ErrExpectedGot(name, "integer", args[1])
} else if den == 0 {
err = errFuncDivisionByZero(name)
err = ErrFuncDivisionByZero(name)
}
}
if err == nil {
@@ -148,7 +148,7 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
case *FractionType:
result = v
default:
err = errCantConvert(name, v, "float")
err = ErrCantConvert(name, v, "float")
}
return
}
@@ -159,28 +159,28 @@ func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err err
func ImportBuiltinsFuncs(ctx ExprContext) {
anyParams := []ExprFuncParam{
newFuncParam(paramValue),
NewFuncParam(ParamValue),
}
ctx.RegisterFunc("isNil", newGolangFunctor(isNilFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isInt", newGolangFunctor(isIntFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isFloat", newGolangFunctor(isFloatFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isBool", newGolangFunctor(isBoolFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isString", newGolangFunctor(isStringFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isFract", newGolangFunctor(isFractionFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isRational", newGolangFunctor(isRationalFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isList", newGolangFunctor(isListFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isDict", newGolangFunctor(isDictionaryFunc), typeBoolean, anyParams)
ctx.RegisterFunc("isNil", NewGolangFunctor(isNilFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isInt", NewGolangFunctor(isIntFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isFloat", NewGolangFunctor(isFloatFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isBool", NewGolangFunctor(isBoolFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isString", NewGolangFunctor(isStringFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isFract", NewGolangFunctor(isFractionFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isRational", NewGolangFunctor(isRationalFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isList", NewGolangFunctor(isListFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isDict", NewGolangFunctor(isDictionaryFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("bool", newGolangFunctor(boolFunc), typeBoolean, anyParams)
ctx.RegisterFunc("int", newGolangFunctor(intFunc), typeInt, anyParams)
ctx.RegisterFunc("dec", newGolangFunctor(decFunc), typeFloat, anyParams)
ctx.RegisterFunc("fract", newGolangFunctor(fractFunc), typeFraction, []ExprFuncParam{
newFuncParam(paramValue),
newFuncParamFlagDef("denominator", pfOptional, 1),
ctx.RegisterFunc("bool", NewGolangFunctor(boolFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("int", NewGolangFunctor(intFunc), TypeInt, anyParams)
ctx.RegisterFunc("dec", NewGolangFunctor(decFunc), TypeFloat, anyParams)
ctx.RegisterFunc("fract", NewGolangFunctor(fractFunc), TypeFraction, []ExprFuncParam{
NewFuncParam(ParamValue),
NewFuncParamFlagDef("denominator", PfDefault, 1),
})
}
func init() {
registerImport("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
RegisterBuiltinModule("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
}
+6 -6
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-fmt.go
// builtin-fmt.go
package expr
import "fmt"
@@ -23,14 +23,14 @@ func printLnFunc(ctx ExprContext, name string, args []any) (result any, err erro
}
func ImportFmtFuncs(ctx ExprContext) {
ctx.RegisterFunc("print", newGolangFunctor(printFunc), typeInt, []ExprFuncParam{
newFuncParamFlag(paramItem, pfRepeat),
ctx.RegisterFunc("print", NewGolangFunctor(printFunc), TypeInt, []ExprFuncParam{
NewFuncParamFlag(ParamItem, PfRepeat),
})
ctx.RegisterFunc("println", newGolangFunctor(printLnFunc), typeInt, []ExprFuncParam{
newFuncParamFlag(paramItem, pfRepeat),
ctx.RegisterFunc("println", NewGolangFunctor(printLnFunc), TypeInt, []ExprFuncParam{
NewFuncParamFlag(ParamItem, PfRepeat),
})
}
func init() {
registerImport("fmt", ImportFmtFuncs, "String and console formatting functions")
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).
// All rights reserved.
// funcs-math.go
// builtin-math-arith.go
package expr
import (
@@ -167,15 +167,15 @@ func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
}
func ImportMathFuncs(ctx ExprContext) {
ctx.RegisterFunc("add", &golangFunctor{f: addFunc}, typeNumber, []ExprFuncParam{
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, int64(0)),
ctx.RegisterFunc("add", &golangFunctor{f: addFunc}, TypeNumber, []ExprFuncParam{
NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, int64(0)),
})
ctx.RegisterFunc("mul", &golangFunctor{f: mulFunc}, typeNumber, []ExprFuncParam{
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, int64(1)),
ctx.RegisterFunc("mul", &golangFunctor{f: mulFunc}, TypeNumber, []ExprFuncParam{
NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, int64(1)),
})
}
func init() {
registerImport("math.arith", ImportMathFuncs, "Functions add() and mul()")
RegisterBuiltinModule("math.arith", ImportMathFuncs, "Functions add() and mul()")
}
+65 -29
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-os.go
// builtin-os-file.go
package expr
import (
@@ -68,7 +68,7 @@ func createFileFunc(ctx ExprContext, name string, args []any) (result any, err e
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
}
} else {
err = errMissingFilePath("createFile")
err = errMissingFilePath(name)
}
return
}
@@ -80,7 +80,7 @@ func openFileFunc(ctx ExprContext, name string, args []any) (result any, err err
result = &osReader{fh: fh, reader: bufio.NewReader(fh)}
}
} else {
err = errMissingFilePath("openFile")
err = errMissingFilePath(name)
}
return
}
@@ -92,7 +92,7 @@ func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err e
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
}
} else {
err = errMissingFilePath("openFile")
err = errMissingFilePath(name)
}
return
}
@@ -118,13 +118,13 @@ func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err er
}
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle("closeFileFunc", handle)
err = errInvalidFileHandle(name, handle)
}
result = err == nil
return
}
func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
func fileWriteTextFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle
var invalidFileHandle any
var ok bool
@@ -142,12 +142,12 @@ func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err er
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle("writeFileFunc", invalidFileHandle)
err = errInvalidFileHandle(name, invalidFileHandle)
}
return
}
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
func fileReadTextFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle
var invalidFileHandle any
var ok bool
@@ -165,50 +165,86 @@ func readFileFunc(ctx ExprContext, name string, args []any) (result any, err err
limit = s[0]
}
if v, err = r.reader.ReadString(limit); err == nil {
if len(v) > 0 && v[len(v)-1] == limit {
v, err = r.reader.ReadString(limit)
if err == io.EOF {
err = nil
}
if len(v) > 0 {
if v[len(v)-1] == limit {
result = v[0 : len(v)-1]
} else {
result = v
}
}
if err == io.EOF {
err = nil
}
} else {
invalidFileHandle = handle
}
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle("readFileFunc", invalidFileHandle)
err = errInvalidFileHandle(name, invalidFileHandle)
}
return
}
func fileReadTextAllFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle
var invalidFileHandle any
var ok bool
result = nil
if handle, ok = args[0].(osHandle); !ok || args[0] == nil {
invalidFileHandle = args[0]
}
if handle != nil {
if r, ok := handle.(*osReader); ok {
var b []byte
b, err = io.ReadAll(r.reader)
result = string(b)
} else {
invalidFileHandle = handle
}
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, invalidFileHandle)
}
return
}
func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("openFile", newGolangFunctor(openFileFunc), typeHandle, []ExprFuncParam{
newFuncParam(paramFilepath),
ctx.RegisterFunc("fileOpen", NewGolangFunctor(openFileFunc), TypeHandle, []ExprFuncParam{
NewFuncParam(ParamFilepath),
})
ctx.RegisterFunc("appendFile", newGolangFunctor(appendFileFunc), typeHandle, []ExprFuncParam{
newFuncParam(paramFilepath),
ctx.RegisterFunc("fileAppend", NewGolangFunctor(appendFileFunc), TypeHandle, []ExprFuncParam{
NewFuncParam(ParamFilepath),
})
ctx.RegisterFunc("createFile", newGolangFunctor(createFileFunc), typeHandle, []ExprFuncParam{
newFuncParam(paramFilepath),
ctx.RegisterFunc("fileCreate", NewGolangFunctor(createFileFunc), TypeHandle, []ExprFuncParam{
NewFuncParam(ParamFilepath),
})
ctx.RegisterFunc("writeFile", newGolangFunctor(writeFileFunc), typeInt, []ExprFuncParam{
newFuncParam(typeHandle),
newFuncParamFlagDef(typeItem, pfOptional|pfRepeat, ""),
ctx.RegisterFunc("fileClose", NewGolangFunctor(closeFileFunc), TypeBoolean, []ExprFuncParam{
NewFuncParam(TypeHandle),
})
ctx.RegisterFunc("readFile", newGolangFunctor(readFileFunc), typeString, []ExprFuncParam{
newFuncParam(typeHandle),
newFuncParamFlagDef("limitCh", pfOptional, "\n"),
ctx.RegisterFunc("fileWriteText", NewGolangFunctor(fileWriteTextFunc), TypeInt, []ExprFuncParam{
NewFuncParam(TypeHandle),
NewFuncParamFlagDef(TypeItem, PfDefault|PfRepeat, ""),
})
ctx.RegisterFunc("closeFile", newGolangFunctor(closeFileFunc), typeBoolean, []ExprFuncParam{
newFuncParam(typeHandle),
ctx.RegisterFunc("fileReadText", NewGolangFunctor(fileReadTextFunc), TypeString, []ExprFuncParam{
NewFuncParam(TypeHandle),
NewFuncParamFlagDef("limitCh", PfDefault, "\n"),
})
ctx.RegisterFunc("fileReadTextAll", NewGolangFunctor(fileReadTextAllFunc), TypeString, []ExprFuncParam{
NewFuncParam(TypeHandle),
})
}
func init() {
registerImport("os.file", ImportOsFuncs, "Operating system file functions")
RegisterBuiltinModule("os.file", ImportOsFuncs, "Operating system file functions")
}
+33 -33
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-string.go
// builtin-string.go
package expr
import (
@@ -21,7 +21,7 @@ func doJoinStr(funcName string, sep string, it Iterator) (result any, err error)
if s, ok := v.(string); ok {
sb.WriteString(s)
} else {
err = errExpectedGot(funcName, typeString, v)
err = ErrExpectedGot(funcName, TypeString, v)
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 {
result, err = doJoinStr(name, sep, it)
} else {
err = errInvalidParameterValue(name, paramParts, args[1])
err = ErrInvalidParameterValue(name, ParamParts, args[1])
}
} else {
result, err = doJoinStr(name, sep, NewArrayIterator(args[1:]))
}
} else {
err = errWrongParamType(name, paramSeparator, typeString, args[0])
err = ErrWrongParamType(name, ParamSeparator, TypeString, args[0])
}
return
}
@@ -63,14 +63,14 @@ func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error
var ok bool
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
}
if count, err = toInt(args[2], name+"()"); err != nil {
if count, err = ToInt(args[2], name+"()"); err != nil {
return
}
@@ -91,7 +91,7 @@ func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err erro
var ok bool
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)
return
@@ -104,7 +104,7 @@ func startsWithStrFunc(ctx ExprContext, name string, args []any) (result any, er
result = false
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:] {
if target, ok := targetSpec.(string); ok {
@@ -127,7 +127,7 @@ func endsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err
result = false
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:] {
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
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 {
@@ -182,42 +182,42 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
// Import above functions in the context
func ImportStringFuncs(ctx ExprContext) {
ctx.RegisterFunc("joinStr", newGolangFunctor(joinStrFunc), typeString, []ExprFuncParam{
newFuncParam(paramSeparator),
newFuncParamFlag(paramItem, pfRepeat),
ctx.RegisterFunc("strJoin", NewGolangFunctor(joinStrFunc), TypeString, []ExprFuncParam{
NewFuncParam(ParamSeparator),
NewFuncParamFlag(ParamItem, PfRepeat),
})
ctx.RegisterFunc("subStr", newGolangFunctor(subStrFunc), typeString, []ExprFuncParam{
newFuncParam(paramSource),
newFuncParamFlagDef(paramStart, pfOptional, int64(0)),
newFuncParamFlagDef(paramCount, pfOptional, int64(-1)),
ctx.RegisterFunc("strSub", NewGolangFunctor(subStrFunc), TypeString, []ExprFuncParam{
NewFuncParam(ParamSource),
NewFuncParamFlagDef(ParamStart, PfDefault, int64(0)),
NewFuncParamFlagDef(ParamCount, PfDefault, int64(-1)),
})
ctx.RegisterFunc("splitStr", newGolangFunctor(splitStrFunc), "list of "+typeString, []ExprFuncParam{
newFuncParam(paramSource),
newFuncParamFlagDef(paramSeparator, pfOptional, ""),
newFuncParamFlagDef(paramCount, pfOptional, int64(-1)),
ctx.RegisterFunc("strSplit", NewGolangFunctor(splitStrFunc), "list of "+TypeString, []ExprFuncParam{
NewFuncParam(ParamSource),
NewFuncParamFlagDef(ParamSeparator, PfDefault, ""),
NewFuncParamFlagDef(ParamCount, PfDefault, int64(-1)),
})
ctx.RegisterFunc("trimStr", newGolangFunctor(trimStrFunc), typeString, []ExprFuncParam{
newFuncParam(paramSource),
ctx.RegisterFunc("strTrim", NewGolangFunctor(trimStrFunc), TypeString, []ExprFuncParam{
NewFuncParam(ParamSource),
})
ctx.RegisterFunc("startsWithStr", newGolangFunctor(startsWithStrFunc), typeBoolean, []ExprFuncParam{
newFuncParam(paramSource),
newFuncParam(paramPrefix),
newFuncParamFlag("other "+paramPrefix, pfRepeat),
ctx.RegisterFunc("strStartsWith", NewGolangFunctor(startsWithStrFunc), TypeBoolean, []ExprFuncParam{
NewFuncParam(ParamSource),
NewFuncParam(ParamPrefix),
NewFuncParamFlag("other "+ParamPrefix, PfRepeat),
})
ctx.RegisterFunc("endsWithStr", newGolangFunctor(endsWithStrFunc), typeBoolean, []ExprFuncParam{
newFuncParam(paramSource),
newFuncParam(paramSuffix),
newFuncParamFlag("other "+paramSuffix, pfRepeat),
ctx.RegisterFunc("strEndsWith", NewGolangFunctor(endsWithStrFunc), TypeBoolean, []ExprFuncParam{
NewFuncParam(ParamSource),
NewFuncParam(ParamSuffix),
NewFuncParamFlag("other "+ParamSuffix, PfRepeat),
})
}
// Register the import function in the import-register.
// That will allow to import all function of this module by the "builtin" operator."
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 -14
View File
@@ -8,7 +8,7 @@ import (
"fmt"
)
func errTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error) {
func ErrTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error) {
if maxArgs < 0 {
err = fmt.Errorf("%s(): too few params -- expected %d or more, got %d", funcName, minArgs, argCount)
} else {
@@ -17,39 +17,39 @@ func errTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error
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)
return
}
// --- General errors
func errCantConvert(funcName string, value any, kind string) error {
return fmt.Errorf("%s(): can't convert %s to %s", funcName, typeName(value), kind)
func ErrCantConvert(funcName string, value any, kind string) error {
return fmt.Errorf("%s(): can't convert %s to %s", funcName, TypeName(value), kind)
}
func errExpectedGot(funcName string, kind string, value any) error {
return fmt.Errorf("%s() expected %s, got %s (%v)", funcName, kind, typeName(value), value)
func ErrExpectedGot(funcName string, kind string, value any) error {
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)
}
func errDivisionByZero() error {
func ErrDivisionByZero() error {
return fmt.Errorf("division by zero")
}
// --- Parameter errors
func errMissingRequiredParameter(funcName, paramName string) error {
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 {
return fmt.Errorf("%s() invalid value %s (%v) for parameter %q", funcName, typeName(paramValue), paramValue, paramName)
func ErrInvalidParameterValue(funcName, paramName string, paramValue any) error {
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 {
return fmt.Errorf("%s() the %q parameter must be a %s, got a %s (%v)", funcName, paramName, paramType, typeName(paramValue), paramValue)
func ErrWrongParamType(funcName, paramName, paramType string, paramValue any) error {
return fmt.Errorf("%s(): the %q parameter must be a %s, got a %s (%v)", funcName, paramName, paramType, TypeName(paramValue), paramValue)
}
+19 -13
View File
@@ -5,17 +5,23 @@
package expr
const (
paramCount = "count"
paramItem = "item"
paramParts = "parts"
paramSeparator = "separator"
paramSource = "source"
paramSuffix = "suffix"
paramPrefix = "prefix"
paramStart = "start"
paramEnd = "end"
paramValue = "value"
paramEllipsis = "..."
paramFilepath = "filepath"
paramDirpath = "dirpath"
ParamCount = "count"
ParamItem = "item"
ParamParts = "parts"
ParamSeparator = "separator"
ParamSource = "source"
ParamSuffix = "suffix"
ParamPrefix = "prefix"
ParamStart = "start"
ParamEnd = "end"
ParamValue = "value"
ParamName = "name"
ParamEllipsis = "..."
ParamFilepath = "filepath"
ParamDirpath = "dirpath"
)
// to be moved in its own source file
const (
ConstLastIndex = 0xFFFF_FFFF
)
+12 -10
View File
@@ -5,14 +5,16 @@
package expr
const (
typeAny = "any"
typeBoolean = "boolean"
typeFloat = "float"
typeFraction = "fraction"
typeHandle = "handle"
typeInt = "integer"
typeItem = "item"
typeNumber = "number"
typePair = "pair"
typeString = "string"
TypeAny = "any"
TypeBoolean = "boolean"
TypeFloat = "float"
TypeFraction = "fraction"
TypeHandle = "handle"
TypeInt = "integer"
TypeItem = "item"
TypeNumber = "number"
TypePair = "pair"
TypeString = "string"
TypeListOf = "list-of-"
TypeListOfStrings = "list-of-strings"
)
+7 -3
View File
@@ -22,13 +22,11 @@ func exportFunc(ctx ExprContext, name string, info ExprFunc) {
if name[0] == '@' {
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())
}
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)
// Export variables
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
@@ -43,3 +41,9 @@ func exportObjects(destCtx, sourceCtx ExprContext) {
}
}
}
func exportObjectsToParent(sourceCtx ExprContext) {
if parentCtx := sourceCtx.GetParent(); parentCtx != nil {
exportObjects(parentCtx, sourceCtx)
}
}
+4
View File
@@ -16,6 +16,7 @@ type Functor interface {
type ExprFuncParam interface {
Name() string
Type() string
IsDefault() bool
IsOptional() bool
IsRepeat() bool
DefaultValue() any
@@ -36,7 +37,10 @@ type ExprFunc interface {
type ExprContext interface {
Clone() ExprContext
Merge(ctx ExprContext)
SetParent(ctx ExprContext)
GetParent() (ctx ExprContext)
GetVar(varName string) (value any, exists bool)
GetLast() any
SetVar(varName string, value any)
UnsafeSetVar(varName string, value any)
EnumVars(func(name string) (accept bool)) (varNames []string)
+11 -40
View File
@@ -4,13 +4,13 @@
// control.go
package expr
import "strings"
// Preset control variables
const (
ControlLastResult = "last"
ControlBoolShortcut = "_bool_shortcut"
ControlImportPath = "_import_path"
ControlPreset = "_preset"
ControlLastResult = "last"
ControlBoolShortcut = "_bool_shortcut"
ControlSearchPath = "_search_path"
ControlParentContext = "_parent_context"
)
// Other control variables
@@ -20,43 +20,14 @@ const (
// Initial values
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) {
if _, exists := ctx.GetVar(ControlPreset); exists {
return
}
ctx.SetVar(ControlPreset, true)
ctx.SetVar(ControlBoolShortcut, true)
ctx.SetVar(ControlImportPath, init_import_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
ctx.SetVar(ControlSearchPath, init_search_path)
}
+50 -29
View File
@@ -12,6 +12,12 @@ import (
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 {
@@ -26,42 +32,52 @@ func newDict(dictAny map[any]*term) (dict *DictType) {
return
}
func (dict *DictType) toMultiLine(sb *strings.Builder, indent int) {
sb.WriteString(strings.Repeat("\t", indent))
sb.WriteString("{\n")
func (dict *DictType) toMultiLine(sb *strings.Builder, opt FmtOpt) {
indent := GetFormatIndent(opt)
flags := GetFormatFlags(opt)
//sb.WriteString(strings.Repeat(" ", indent))
sb.WriteByte('{')
first := true
for name, value := range *dict {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
if len(*dict) > 0 {
innerOpt := MakeFormatOptions(flags, indent+1)
nest := strings.Repeat(" ", indent+1)
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))
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(strings.Repeat("\t", indent))
sb.WriteString("\n}")
sb.WriteString("}")
}
func (dict *DictType) ToString(opt FmtOpt) string {
var sb strings.Builder
if opt&MultiLine != 0 {
dict.toMultiLine(&sb, 0)
flags := GetFormatFlags(opt)
if flags&MultiLine != 0 {
dict.toMultiLine(&sb, opt)
} else {
sb.WriteByte('{')
first := true
@@ -124,7 +140,12 @@ func (dict *DictType) merge(second *DictType) {
}
func (dict *DictType) setItem(key any, value any) (err error) {
(*dict)[key]=value
(*dict)[key] = value
return
}
////////////////
type DictFormat interface {
ToDict() *DictType
}
+60 -26
View File
@@ -22,18 +22,45 @@ Expressions calculator
toc::[]
#TODO: Work in progress (last update on 2024/06/02, 08:18 a.m.)#
#TODO: Work in progress (last update on 2024/06/17, 16:31 a.m.)#
== Expr
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
=== Concepts and terminology
#TODO#
Expressions are texts containing sequences of operations represented by a syntax very similar to that of most programming languages. _Expr_ package provides these macro functions:
* *_Scanner_* -- Its input is a text. It scans expression text characters to produce a flow of logical symbol and related attributes, aka tokens.
* *_Parser_* -- Parser input is the token flow coming from the scanner. It analyses the token flow verifyng if it complies with the _Expr_ syntax. If that is the case, the Parser generates the Abstract Syntax Tree (AST). This is tree data structure that represents the components of an expressions and how they are related one each other.
* *_Calculator_*. Its input is the AST. It computes the parsed expression contained in the AST and returns the result or an error.
image::expression-diagram.png[]
==== Variables
_Expr_ supports variables. The result of an expression can be stored in a variable and reused in other espressions simply specifying the name of the variable as an operand.
==== Multi-expression
An input text valid for _Expr_ can contain more than an expression. Expressions are separated by [blue]`;` (semicolon). When an input contains two or more expressions it is called _multi-expression_.
_Expr_ parses and computes each expression of a multi-espression, from the left to the right. If all expressions are computed without errors, it only returns the value of the last, the right most.
The result of each expression of a multi-expression is stored in an automatic variable named _last_. In this way, each expression can refer to the result of the previous one without the need to assign that value to a new dedicated variable.
==== Calculation context
All objects, such as variables and functions, created during the calculation of an expression are stored in a memory called _context_.
The expression context is analogous to the stack-frame of other programming languages. When a function is called, a new context is allocated to store local definitions.
Function contexts are created by cloning the calling context. More details on this topic are given later in this document.
_Expr_ creates and keeps a inner _global context_ where it stores imported functions, either from builtin or plugin modules. To perform calculations, the calling program must provide its own context; this is the _main context_. All calculations take place in this context. As mentioned eralier, when a function is called, a new context is created by cloning the calling context. The createt context can be called _function context_.
Imported functions are registerd in the _global context_. When an expression first calls an imported function, that function is linked to the current context; this can be the _main context_ or a _function context_.
=== `dev-expr` test tool
`dev-expr` is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, in additon to the automatic verification test suite based on the Go test framework, `dev-expr` provides an important aid for quickly testing of new features during their development.
`dev-expr` is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, in additon to the automatic verification test suite based on the Go test framework, `dev-expr` provided an important aid for quickly testing of new features during their development.
`dev-expr` can work as a _REPL_, _**R**ead-**E**xecute-**P**rint-**L**oop_, or it can process expression acquired from files or standard input.
@@ -47,11 +74,10 @@ Here are some examples of execution.
# Type 'exit' or Ctrl+D to quit the program.
[user]$ ./dev-expr
expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@portale-stac.it)
Based on the Expr package v0.10.0
dev-expr -- Expressions calculator v1.10.0(build 14),2024/06/17 (celestino.amoroso@portale-stac.it)
Based on the Expr package v0.19.0
Type help to get the list of available commands
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
>>> help
--- REPL commands:
source -- Load a file as input
@@ -61,6 +87,7 @@ expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@por
help -- Show command list
ml -- Enable/Disable multi-line output
mods -- List builtin modules
output -- Enable/Disable printing expression results. Options 'on', 'off', 'status'
--- Command line options:
-b <builtin> Import builtin modules.
@@ -70,9 +97,10 @@ expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@por
-i Force REPL operation when all -e occurences have been processed
-h, --help, help Show this help menu
-m, --modules List all builtin modules
--noout Disable printing of expression results
-p Print prefix form
-t Print tree form <2>
-v, --version Show program version
>>>
----
@@ -83,8 +111,8 @@ expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@por
[source,shell]
----
[user]$ ./dev-expr
expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@portale-stac.it)
Based on the Expr package v0.10.0
dev-expr -- Expressions calculator v1.10.0(build 14),2024/06/17 (celestino.amoroso@portale-stac.it)
Based on the Expr package v0.19.0
Type help to get the list of available commands
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
@@ -101,21 +129,21 @@ expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@por
7
-
6
>>> 1+2 but 5|2+0.5 <4>
>>> 4+2 but 5|2+0.5 <4>
3
>>> 1+2; 5|2+0.5 <5>
>>> 4+2; 5|2+0.5 <5>
3
>>>
----
<1> Number bases: 0x = hexadecimal, 0o = octal, 0b = binary.
<2> Fractions: numerator | denominator.
<1> Number bases: 0x = _hexadecimal_, 0o = _octal_, 0b = _binary_.
<2> Fractions: _numerator_ | _denominator_.
<3> Activate multi-line output of fractions.
<4> But operator, see <<_but_operator>>.
<5> Multi-expression: the same result of the previous single expression but this it is obtained with two separated calculations.
== Data types
_Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists.
_Expr_ has its type system which is a subset of Golang's type system. It supports numerical, string, relational, boolean expressions, and mixed-type lists and maps.
=== Numbers
_Expr_ supports three type of numbers:
@@ -272,25 +300,31 @@ Some arithmetic operators can also be used with strings.
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` -> _"oneone"_
|===
The items of strings can be accessed using the dot `.` operator.
The items of strings can be accessed using the square `[]` operator.
.Item access syntax
====
_item_ = _string-expr_ "**.**" _integer-expr_
_item_ = _string-expr_ "**[**" _integer-expr_ "**]**"
====
.Sub string syntax
====
_sub-string_ = _string-expr_ "**[**" _integer-expr_ "**:**" _integer-expr_ "**]**"
====
.String examples
`>>>` [blue]`s="abc"` [gray]_// assign the string to variable s_ +
[green]`abc` +
`>>>` [blue]`s.1` [gray]_// char at position 1 (starting from 0)_ +
[green]`b` +
`>>>` [blue]`s.(-1)` [gray]_// char at position -1, the rightmost one_ +
[green]`c` +
`>>>` [blue]`s="abcd"` [gray]_// assign the string to variable s_ +
[green]`"abcd"` +
`>>>` [blue]`s[1]` [gray]_// char at position 1 (starting from 0)_ +
[green]`"b"` +
`>>>` [blue]`s.[-1]` [gray]_// char at position -1, the rightmost one_ +
[green]`"d"` +
`>>>` [blue]`\#s` [gray]_// number of chars_ +
[gren]`3` +
`>>>` [blue]`#"abc"` [gray]_// number of chars_ +
[green]`3`
[gren]`4` +
`>>>` [blue]`#"abc"` [gray]_// number of chars_ +
[green]`3` +
`>>>` [blue]`s[1:3]` [gray]_// chars from position 1 to position 3 excluded_ +
[grean]`"bc"`
=== Booleans
Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relational and boolean expressions result in boolean values.
+99 -33
View File
@@ -535,7 +535,13 @@ pre.rouge .ss {
<ul class="sectlevel1">
<li><a href="#_expr">1. Expr</a>
<ul class="sectlevel2">
<li><a href="#_concepts_and_terminology">1.1. Concepts and terminology</a></li>
<li><a href="#_concepts_and_terminology">1.1. Concepts and terminology</a>
<ul class="sectlevel3">
<li><a href="#_variables">1.1.1. Variables</a></li>
<li><a href="#_multi_expression">1.1.2. Multi-expression</a></li>
<li><a href="#_calculation_context">1.1.3. Calculation context</a></li>
</ul>
</li>
<li><a href="#_dev_expr_test_tool">1.2. <code>dev-expr</code> test tool</a></li>
</ul>
</li>
@@ -554,7 +560,7 @@ pre.rouge .ss {
<li><a href="#_dictionaries">2.5. Dictionaries</a></li>
</ul>
</li>
<li><a href="#_variables">3. Variables</a></li>
<li><a href="#_variables_2">3. Variables</a></li>
<li><a href="#_other_operations">4. Other operations</a>
<ul class="sectlevel2">
<li><a href="#_operator">4.1. <code class="blue">;</code> operator</a></li>
@@ -585,7 +591,7 @@ pre.rouge .ss {
<div class="sectionbody">
<!-- toc disabled -->
<div class="paragraph">
<p><mark>TODO: Work in progress (last update on 2024/06/02, 08:18 a.m.)</mark></p>
<p><mark>TODO: Work in progress (last update on 2024/06/17, 16:31 a.m.)</mark></p>
</div>
</div>
</div>
@@ -598,18 +604,67 @@ pre.rouge .ss {
<div class="sect2">
<h3 id="_concepts_and_terminology"><a class="anchor" href="#_concepts_and_terminology"></a><a class="link" href="#_concepts_and_terminology">1.1. Concepts and terminology</a></h3>
<div class="paragraph">
<p><mark>TODO</mark></p>
<p>Expressions are texts containing sequences of operations represented by a syntax very similar to that of most programming languages. <em>Expr</em> package provides these macro functions:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><strong><em>Scanner</em></strong>&#8201;&#8212;&#8201;Its input is a text. It scans expression text characters to produce a flow of logical symbol and related attributes, aka tokens.</p>
</li>
<li>
<p><strong><em>Parser</em></strong>&#8201;&#8212;&#8201;Parser input is the token flow coming from the scanner. It analyses the token flow verifyng if it complies with the <em>Expr</em> syntax. If that is the case, the Parser generates the Abstract Syntax Tree (AST). This is tree data structure that represents the components of an expressions and how they are related one each other.</p>
</li>
<li>
<p><strong><em>Calculator</em></strong>. Its input is the AST. It computes the parsed expression contained in the AST and returns the result or an error.</p>
</li>
</ul>
</div>
<div class="imageblock">
<div class="content">
<img src="expression-diagram.png" alt="expression diagram">
</div>
</div>
<div class="sect3">
<h4 id="_variables"><a class="anchor" href="#_variables"></a><a class="link" href="#_variables">1.1.1. Variables</a></h4>
<div class="paragraph">
<p><em>Expr</em> supports variables. The result of an expression can be stored in a variable and reused in other espressions simply specifying the name of the variable as an operand.</p>
</div>
</div>
<div class="sect3">
<h4 id="_multi_expression"><a class="anchor" href="#_multi_expression"></a><a class="link" href="#_multi_expression">1.1.2. Multi-expression</a></h4>
<div class="paragraph">
<p>An input text valid for <em>Expr</em> can contain more than an expression. Expressions are separated by <code class="blue">;</code> (semicolon). When an input contains two or more expressions it is called <em>multi-expression</em>.</p>
</div>
<div class="paragraph">
<p><em>Expr</em> parses and computes each expression of a multi-espression, from the left to the right. If all expressions are computed without errors, it only returns the value of the last, the right most.</p>
</div>
<div class="paragraph">
<p>The result of each expression of a multi-expression is stored in an automatic variable named <em>last</em>. In this way, each expression can refer to the result of the previous one without the need to assign that value to a new dedicated variable.</p>
</div>
</div>
<div class="sect3">
<h4 id="_calculation_context"><a class="anchor" href="#_calculation_context"></a><a class="link" href="#_calculation_context">1.1.3. Calculation context</a></h4>
<div class="paragraph">
<p>All objects, such as variables and functions, created during the calculation of an expression are stored in a memory called <em>context</em>.</p>
</div>
<div class="paragraph">
<p>The expression context is analogous to the stack-frame of other programming languages. When a function is called, a new context is allocated to store local definitions.</p>
</div>
<div class="paragraph">
<p>Function contexts are created by cloning the calling context. More details on this topic are given later in this document.</p>
</div>
<div class="paragraph">
<p><em>Expr</em> creates and keeps a inner <em>global context</em> where it stores imported functions, either from builtin or plugin modules. To perform calculations, the calling program must provide its own context; this is the <em>main context</em>. All calculations take place in this context. As mentioned eralier, when a function is called, a new context is created by cloning the calling context. The createt context can be called <em>function context</em>.</p>
</div>
<div class="paragraph">
<p>Imported functions are registerd in the <em>global context</em>. When an expression first calls an imported function, that function is linked to the current context; this can be the <em>main context</em> or a <em>function context</em>.</p>
</div>
</div>
</div>
<div class="sect2">
<h3 id="_dev_expr_test_tool"><a class="anchor" href="#_dev_expr_test_tool"></a><a class="link" href="#_dev_expr_test_tool">1.2. <code>dev-expr</code> test tool</a></h3>
<div class="paragraph">
<p><code>dev-expr</code> is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, in additon to the automatic verification test suite based on the Go test framework, <code>dev-expr</code> provides an important aid for quickly testing of new features during their development.</p>
<p><code>dev-expr</code> is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, in additon to the automatic verification test suite based on the Go test framework, <code>dev-expr</code> provided an important aid for quickly testing of new features during their development.</p>
</div>
<div class="paragraph">
<p><code>dev-expr</code> can work as a <em>REPL</em>, <em><strong>R</strong>ead-<strong>E</strong>xecute-<strong>P</strong>rint-<strong>L</strong>oop</em>, or it can process expression acquired from files or standard input.</p>
@@ -626,11 +681,10 @@ pre.rouge .ss {
<pre class="rouge highlight"><code data-lang="shell"><span class="c"># Type 'exit' or Ctrl+D to quit the program.</span>
<span class="o">[</span>user]<span class="nv">$ </span>./dev-expr
<span class="nb">expr</span> <span class="nt">--</span> Expressions calculator v1.7.1<span class="o">(</span>build 2<span class="o">)</span>,2024/05/16 <span class="o">(</span>celestino.amoroso@portale-stac.it<span class="o">)</span>
Based on the Expr package v0.10.0
dev-expr <span class="nt">--</span> Expressions calculator v1.10.0<span class="o">(</span>build 14<span class="o">)</span>,2024/06/17 <span class="o">(</span>celestino.amoroso@portale-stac.it<span class="o">)</span>
Based on the Expr package v0.19.0
Type <span class="nb">help </span>to get the list of available commands
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
<span class="o">&gt;&gt;&gt;</span> <span class="nb">help</span>
<span class="nt">---</span> REPL commands:
<span class="nb">source</span> <span class="nt">--</span> Load a file as input
@@ -640,6 +694,7 @@ pre.rouge .ss {
<span class="nb">help</span> <span class="nt">--</span> Show <span class="nb">command </span>list
ml <span class="nt">--</span> Enable/Disable multi-line output
mods <span class="nt">--</span> List <span class="nb">builtin </span>modules
output <span class="nt">--</span> Enable/Disable printing expression results. Options <span class="s1">'on'</span>, <span class="s1">'off'</span>, <span class="s1">'status'</span>
<span class="nt">---</span> Command line options:
<span class="nt">-b</span> &lt;<span class="nb">builtin</span><span class="o">&gt;</span> Import <span class="nb">builtin </span>modules.
@@ -649,9 +704,10 @@ pre.rouge .ss {
<span class="nt">-i</span> Force REPL operation when all <span class="nt">-e</span> occurences have been processed
<span class="nt">-h</span>, <span class="nt">--help</span>, <span class="nb">help </span>Show this <span class="nb">help </span>menu
<span class="nt">-m</span>, <span class="nt">--modules</span> List all <span class="nb">builtin </span>modules
<span class="nt">--noout</span> Disable printing of expression results
<span class="nt">-p</span> Print prefix form
<span class="nt">-t</span> Print tree form <i class="conum" data-value="2"></i><b>(2)</b>
<span class="nt">-v</span>, <span class="nt">--version</span> Show program version
<span class="o">&gt;&gt;&gt;</span></code></pre>
</div>
</div>
@@ -671,8 +727,8 @@ pre.rouge .ss {
<div class="title">REPL examples</div>
<div class="content">
<pre class="rouge highlight"><code data-lang="shell"><span class="o">[</span>user]<span class="nv">$ </span>./dev-expr
<span class="nb">expr</span> <span class="nt">--</span> Expressions calculator v1.7.1<span class="o">(</span>build 2<span class="o">)</span>,2024/05/16 <span class="o">(</span>celestino.amoroso@portale-stac.it<span class="o">)</span>
Based on the Expr package v0.10.0
dev-expr <span class="nt">--</span> Expressions calculator v1.10.0<span class="o">(</span>build 14<span class="o">)</span>,2024/06/17 <span class="o">(</span>celestino.amoroso@portale-stac.it<span class="o">)</span>
Based on the Expr package v0.19.0
Type <span class="nb">help </span>to get the list of available commands
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
@@ -689,9 +745,9 @@ pre.rouge .ss {
7
-
6
<span class="o">&gt;&gt;&gt;</span> 1+2 but 5|2+0.5 <i class="conum" data-value="4"></i><b>(4)</b>
<span class="o">&gt;&gt;&gt;</span> 4+2 but 5|2+0.5 <i class="conum" data-value="4"></i><b>(4)</b>
3
<span class="o">&gt;&gt;&gt;</span> 1+2<span class="p">;</span> 5|2+0.5 <i class="conum" data-value="5"></i><b>(5)</b>
<span class="o">&gt;&gt;&gt;</span> 4+2<span class="p">;</span> 5|2+0.5 <i class="conum" data-value="5"></i><b>(5)</b>
3
<span class="o">&gt;&gt;&gt;</span></code></pre>
</div>
@@ -700,11 +756,11 @@ pre.rouge .ss {
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>Number bases: 0x = hexadecimal, 0o = octal, 0b = binary.</td>
<td>Number bases: 0x = <em>hexadecimal</em>, 0o = <em>octal</em>, 0b = <em>binary</em>.</td>
</tr>
<tr>
<td><i class="conum" data-value="2"></i><b>2</b></td>
<td>Fractions: numerator | denominator.</td>
<td>Fractions: <em>numerator</em> | <em>denominator</em>.</td>
</tr>
<tr>
<td><i class="conum" data-value="3"></i><b>3</b></td>
@@ -727,7 +783,7 @@ pre.rouge .ss {
<h2 id="_data_types"><a class="anchor" href="#_data_types"></a><a class="link" href="#_data_types">2. Data types</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p><em>Expr</em> supports numerical, string, relational, boolean expressions, and mixed-type lists.</p>
<p><em>Expr</em> has its type system which is a subset of Golang&#8217;s type system. It supports numerical, string, relational, boolean expressions, and mixed-type lists and maps.</p>
</div>
<div class="sect2">
<h3 id="_numbers"><a class="anchor" href="#_numbers"></a><a class="link" href="#_numbers">2.1. Numbers</a></h3>
@@ -1011,28 +1067,38 @@ pre.rouge .ss {
</tbody>
</table>
<div class="paragraph">
<p>The items of strings can be accessed using the dot <code>.</code> operator.</p>
<p>The items of strings can be accessed using the square <code>[]</code> operator.</p>
</div>
<div class="exampleblock">
<div class="title">Example 4. Item access syntax</div>
<div class="content">
<div class="paragraph">
<p><em>item</em> = <em>string-expr</em> "<strong>.</strong>" <em>integer-expr</em></p>
<p><em>item</em> = <em>string-expr</em> "<strong>[</strong>" <em>integer-expr</em> "<strong>]</strong>"</p>
</div>
</div>
</div>
<div class="exampleblock">
<div class="title">Example 5. Sub string syntax</div>
<div class="content">
<div class="paragraph">
<p><em>sub-string</em> = <em>string-expr</em> "<strong>[</strong>" <em>integer-expr</em> "<strong>:</strong>" <em>integer-expr</em> "<strong>]</strong>"</p>
</div>
</div>
</div>
<div class="paragraph">
<div class="title">String examples</div>
<p><code>&gt;&gt;&gt;</code> <code class="blue">s="abc"</code> <em class="gray">// assign the string to variable s</em><br>
<code class="green">abc</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">s.1</code> <em class="gray">// char at position 1 (starting from 0)</em><br>
<code class="green">b</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">s.(-1)</code> <em class="gray">// char at position -1, the rightmost one</em><br>
<code class="green">c</code><br>
<p><code>&gt;&gt;&gt;</code> <code class="blue">s="abcd"</code> <em class="gray">// assign the string to variable s</em><br>
<code class="green">"abcd"</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">s[1]</code> <em class="gray">// char at position 1 (starting from 0)</em><br>
<code class="green">"b"</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">s.[-1]</code> <em class="gray">// char at position -1, the rightmost one</em><br>
<code class="green">"d"</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">#s</code> <em class="gray">// number of chars</em><br>
<code class="gren">3</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">#"abc"</code> <em class="gray">// number of chars</em><br>
<code class="green">3</code></p>
<code class="gren">4</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">#"abc"</code> <em class="gray">// number of chars</em><br>
<code class="green">3</code><br>
<code>&gt;&gt;&gt;</code> <code class="blue">s[1:3]</code> <em class="gray">// chars from position 1 to position 3 excluded</em><br>
<code class="grean">"bc"</code></p>
</div>
</div>
<div class="sect2">
@@ -1179,7 +1245,7 @@ pre.rouge .ss {
<p><em>Expr</em> supports list of mixed-type values, also specified by normal expressions. Internally, <em>Expr</em>'s lists are Go arrays.</p>
</div>
<div class="exampleblock">
<div class="title">Example 5. List literal syntax</div>
<div class="title">Example 6. List literal syntax</div>
<div class="content">
<div class="paragraph">
<p><strong><em>list</em></strong> = <em>empty-list</em> | <em>non-empty-list</em><br>
@@ -1261,7 +1327,7 @@ pre.rouge .ss {
<p>The items of array can be accessed using the dot <code>.</code> operator.</p>
</div>
<div class="exampleblock">
<div class="title">Example 6. Item access syntax</div>
<div class="title">Example 7. Item access syntax</div>
<div class="content">
<div class="paragraph">
<p><em>item</em> = <em>list-expr</em> "<strong>.</strong>" <em>integer-expr</em></p>
@@ -1297,7 +1363,7 @@ pre.rouge .ss {
<p>Dictionary literals are sequences of pairs separated by comma <code class="blue">,</code> enclosed between brace brackets.</p>
</div>
<div class="exampleblock">
<div class="title">Example 7. Dict literal syntax</div>
<div class="title">Example 8. Dict literal syntax</div>
<div class="content">
<div class="paragraph">
<p><strong><em>dict</em></strong> = <em>empty-dict</em> | <em>non-empty-dict</em><br>
@@ -1357,13 +1423,13 @@ pre.rouge .ss {
</div>
</div>
<div class="sect1">
<h2 id="_variables"><a class="anchor" href="#_variables"></a><a class="link" href="#_variables">3. Variables</a></h2>
<h2 id="_variables_2"><a class="anchor" href="#_variables_2"></a><a class="link" href="#_variables_2">3. Variables</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p><em>Expr</em>, like most programming languages, supports variables. A variable is an identifier with an assigned value. Variables are stored in <em>contexts</em>.</p>
</div>
<div class="exampleblock">
<div class="title">Example 8. Variable literal syntax</div>
<div class="title">Example 9. Variable literal syntax</div>
<div class="content">
<div class="paragraph">
<p><strong><em>variable</em></strong> = <em>identifier</em> "<strong>=</strong>" <em>any-value</em><br>
@@ -1884,7 +1950,7 @@ These operators have a high priority, in particular higher than the operator <co
</div>
<div id="footer">
<div id="footer-text">
Last updated 2024-06-03 06:26:03 +0200
Last updated 2024-06-19 09:33:41 +0200
</div>
</div>
</body>
-33
View File
@@ -1,33 +0,0 @@
builtin ["os.file", "base"];
readInt=func(fh){
line=readFile(fh);
line ? [nil] {nil} :: {int(line)}
};
ds={
"init":func(filename){
fh=openFile(filename);
fh ? [nil] {nil} :: { @current=readInt(fh); @prev=@current };
fh
},
"current":func(){
prev
},
"next":func(fh){
current ?
[nil] {current}
:: {@prev=current; @current=readInt(fh) but current}
},
"clean":func(fh){
closeFile(fh)
}
}
//;f=$(ds, "int.list")
/*
;f++
;f++
;f++
*/
//;add(f)
+14 -2
View File
@@ -6,7 +6,7 @@ package expr
import "fmt"
type FmtOpt uint16
type FmtOpt uint32 // lower 16 bits hold a bit-mask, higher 16 bits hold an indentation number
const (
TTY FmtOpt = 1 << iota
@@ -30,6 +30,18 @@ func TruncateString(s string) (trunc string) {
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 {
ToString(options FmtOpt) string
}
@@ -51,7 +63,7 @@ type Typer interface {
TypeName() string
}
func typeName(v any) (name string) {
func TypeName(v any) (name string) {
if v == nil {
name = "nil"
} else if typer, ok := v.(Typer); ok {
+4 -4
View File
@@ -70,7 +70,7 @@ func makeGeneratingFraction(s string) (f *FractionType, err error) {
}
for _, c := range dec[0:lsd] {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
return nil, ErrExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
den = den * 10
@@ -81,7 +81,7 @@ func makeGeneratingFraction(s string) (f *FractionType, err error) {
mul := int64(1)
for _, c := range subParts[0] {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
return nil, ErrExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
sub = sub*10 + int64(c-'0')
@@ -94,7 +94,7 @@ func makeGeneratingFraction(s string) (f *FractionType, err error) {
p := subParts[1][0 : len(subParts[1])-1]
for _, c := range p {
if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c)
return nil, ErrExpectedGot("fract", "digit", c)
}
num = num*10 + int64(c-'0')
den = den*10 + 9
@@ -212,7 +212,7 @@ func anyToFract(v any) (f *FractionType, err error) {
}
}
if f == nil {
err = errExpectedGot("fract", typeFraction, v)
err = ErrExpectedGot("fract", TypeFraction, v)
}
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(paramFilepath, pfRepeat),
})
ctx.RegisterFunc("importAll", newGolangFunctor(importAllFunc), typeAny, []ExprFuncParam{
newFuncParamFlag(paramFilepath, pfRepeat),
})
}
func init() {
registerImport("import", ImportImportFuncs, "Functions import() and include()")
}
+57 -66
View File
@@ -21,7 +21,7 @@ func (functor *baseFunctor) ToString(opt FmtOpt) (s string) {
if functor.info != nil {
s = functor.info.ToString(opt)
} else {
s = "func() {<body>}"
s = "func() {}"
}
return s
}
@@ -42,65 +42,13 @@ func (functor *baseFunctor) GetFunc() ExprFunc {
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
type paramFlags uint16
const (
pfOptional paramFlags = 1 << iota
pfRepeat
PfDefault paramFlags = 1 << iota
PfOptional
PfRepeat
)
type funcParamInfo struct {
@@ -109,15 +57,15 @@ type funcParamInfo struct {
defaultValue any
}
func newFuncParam(name string) ExprFuncParam {
func NewFuncParam(name string) ExprFuncParam {
return &funcParamInfo{name: name}
}
func newFuncParamFlag(name string, flags paramFlags) ExprFuncParam {
func NewFuncParamFlag(name string, flags paramFlags) ExprFuncParam {
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}
}
@@ -126,15 +74,19 @@ func (param *funcParamInfo) Name() 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 {
return (param.flags & pfOptional) != 0
return (param.flags & PfOptional) != 0
}
func (param *funcParamInfo) IsRepeat() bool {
return (param.flags & pfRepeat) != 0
return (param.flags & PfRepeat) != 0
}
func (param *funcParamInfo) DefaultValue() any {
@@ -159,7 +111,7 @@ func newFuncInfo(name string, functor Functor, returnType string, params []ExprF
if maxArgs == -1 {
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++
} else if maxArgs == minArgs {
minArgs++
@@ -207,7 +159,7 @@ func (info *funcInfo) ToString(opt FmtOpt) string {
}
sb.WriteString(p.Name())
if p.IsOptional() {
if p.IsDefault() {
sb.WriteByte('=')
if s, ok := p.DefaultValue().(string); ok {
sb.WriteByte('"')
@@ -226,9 +178,9 @@ func (info *funcInfo) ToString(opt FmtOpt) string {
if len(info.returnType) > 0 {
sb.WriteString(info.returnType)
} else {
sb.WriteString(typeAny)
sb.WriteString(TypeAny)
}
sb.WriteString(" {<body>}")
sb.WriteString(" {}")
return sb.String()
}
@@ -247,3 +199,42 @@ func (info *funcInfo) MaxArgs() int {
func (info *funcInfo) Functor() Functor {
return info.functor
}
// ----- Call a function ---
func checkFunctionCall(ctx ExprContext, name string, varParams *[]any) (err error) {
if info, exists, owner := GetFuncInfo(ctx, name); exists {
passedCount := len(*varParams)
if info.MinArgs() > passedCount {
err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
}
for i, p := range info.Params() {
if i >= passedCount {
if !p.IsDefault() {
break
}
*varParams = append(*varParams, p.DefaultValue())
}
}
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(*varParams) {
err = ErrTooMuchParams(name, info.MaxArgs(), len(*varParams))
}
if err == nil && owner != ctx {
ctx.RegisterFuncInfo(info)
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
func CallFunction(parentCtx ExprContext, name string, params []any) (result any, err error) {
ctx := cloneContext(parentCtx)
ctx.SetParent(parentCtx)
if err = checkFunctionCall(ctx, name, &params); err == nil {
result, err = ctx.Call(name, params)
exportObjectsToParent(ctx)
}
return
}
+78 -4
View File
@@ -4,13 +4,16 @@
// global-context.go
package expr
import "path/filepath"
import (
"path/filepath"
"strings"
)
var globalCtx *SimpleStore
func ImportInContext(name string) (exists bool) {
var mod *module
if mod, exists = moduleRegister[name]; exists {
var mod *builtinModule
if mod, exists = builtinModuleRegister[name]; exists {
mod.importFunc(globalCtx)
mod.imported = true
}
@@ -19,7 +22,7 @@ func ImportInContext(name string) (exists bool) {
func ImportInContextByGlobPattern(pattern string) (count int, err error) {
var matched bool
for name, mod := range moduleRegister {
for name, mod := range builtinModuleRegister {
if matched, err = filepath.Match(pattern, name); err == nil {
if matched {
count++
@@ -63,7 +66,78 @@ func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool, owne
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
}
func init() {
globalCtx = NewSimpleStore()
initDefaultVars(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)
scanner := NewScanner(r, DefaultTranslations())
parser := NewParser(ctx)
parser := NewParser()
if tree, err = parser.Parse(scanner); err == nil {
result, err = tree.eval(ctx, true)
result, err = tree.Eval(ctx)
}
return
}
@@ -38,10 +38,10 @@ func EvalStringV(source string, args []Arg) (result any, err error) {
for _, arg := range args {
if isFunc(arg.Value) {
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, typeAny, []ExprFuncParam{
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 0),
ctx.RegisterFunc(arg.Name, functor, TypeAny, []ExprFuncParam{
NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, 0),
})
} else {
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) {
var tree *ast
scanner := NewScanner(r, DefaultTranslations())
parser := NewParser(ctx)
parser := NewParser()
if tree, err = parser.Parse(scanner); err == nil {
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
}
+3 -3
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}
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 {
i = listLen + i
}
it.start = i
}
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 {
i = listLen + i
}
it.stop = i
}
if argc >= 3 {
if i, err := toInt(args[2], "step"); err == nil {
if i, err := ToInt(args[2], "step"); err == nil {
if i < 0 {
i = -i
}
+38 -6
View File
@@ -20,6 +20,10 @@ func newListA(listAny ...any) (list *ListType) {
}
func newList(listAny []any) (list *ListType) {
return NewList(listAny)
}
func NewList(listAny []any) (list *ListType) {
if listAny != nil {
ls := make(ListType, len(listAny))
for i, item := range listAny {
@@ -30,17 +34,42 @@ func newList(listAny []any) (list *ListType) {
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) {
indent := GetFormatIndent(opt)
flags := GetFormatFlags(opt)
var sb strings.Builder
sb.WriteByte('[')
if len(*ls) > 0 {
if opt&MultiLine != 0 {
sb.WriteString("\n ")
innerOpt := MakeFormatOptions(flags, indent+1)
nest := strings.Repeat(" ", indent+1)
if flags&MultiLine != 0 {
sb.WriteByte('\n')
sb.WriteString(nest)
}
for i, item := range []any(*ls) {
if i > 0 {
if opt&MultiLine != 0 {
sb.WriteString(",\n ")
if flags&MultiLine != 0 {
sb.WriteString(",\n")
sb.WriteString(nest)
} else {
sb.WriteString(", ")
}
@@ -49,17 +78,20 @@ func (ls *ListType) ToString(opt FmtOpt) (s string) {
sb.WriteByte('"')
sb.WriteString(s)
sb.WriteByte('"')
} else if formatter, ok := item.(Formatter); ok {
sb.WriteString(formatter.ToString(innerOpt))
} else {
sb.WriteString(fmt.Sprintf("%v", item))
}
}
if opt&MultiLine != 0 {
if flags&MultiLine != 0 {
sb.WriteByte('\n')
sb.WriteString(strings.Repeat(" ", indent))
}
}
sb.WriteByte(']')
s = sb.String()
if opt&Truncate != 0 && len(s) > TruncateSize {
if flags&Truncate != 0 && len(s) > TruncateSize {
s = TruncateString(s)
}
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)
}
}
+4 -36
View File
@@ -6,7 +6,6 @@ package expr
import (
"errors"
"fmt"
)
// -------- function call term
@@ -22,34 +21,7 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
}
// -------- eval func call
func checkFunctionCall(ctx ExprContext, name string, varParams *[]any) (err error) {
if info, exists, owner := GetFuncInfo(ctx, name); exists {
passedCount := len(*varParams)
if info.MinArgs() > passedCount {
err = errTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
}
for i, p := range info.Params() {
if i >= passedCount {
if !p.IsOptional() {
break
}
*varParams = append(*varParams, p.DefaultValue())
}
}
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(*varParams) {
err = errTooMuchParams(name, info.MaxArgs(), len(*varParams))
}
if err == nil && owner != ctx {
ctx.RegisterFuncInfo(info)
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
ctx := cloneContext(parentCtx)
func evalFuncCall(ctx ExprContext, self *term) (v any, err error) {
name, _ := self.tk.Value.(string)
params := make([]any, len(self.children), len(self.children)+5)
for i, tree := range self.children {
@@ -61,11 +33,7 @@ func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
}
if err == nil {
if err = checkFunctionCall(ctx, name, &params); err == nil {
if v, err = ctx.Call(name, params); err == nil {
exportObjects(parentCtx, ctx)
}
}
v, err = CallFunction(ctx, name, params)
}
return
}
@@ -91,12 +59,12 @@ func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
var defValue any
flags := paramFlags(0)
if len(param.children) > 0 {
flags |= pfOptional
flags |= PfDefault
if defValue, err = param.children[0].compute(ctx); err != nil {
return
}
}
info := newFuncParamFlagDef(param.source(), flags, defValue)
info := NewFuncParamFlagDef(param.source(), flags, defValue)
paramList = append(paramList, info)
}
v = newExprFunctor(expr, paramList, ctx)
+3 -3
View File
@@ -28,7 +28,7 @@ func assignCollectionItem(ctx ExprContext, collectionTerm, keyListTerm *term, va
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))
err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, TypeName(keyListValue))
return
}
if keyValue = (*keyList)[0]; keyValue == nil {
@@ -41,7 +41,7 @@ func assignCollectionItem(ctx ExprContext, collectionTerm, keyListTerm *term, va
if index, ok := keyValue.(int64); ok {
err = collection.setItem(index, value)
} else {
err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, typeName(keyValue))
err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, TypeName(keyValue))
}
case *DictType:
err = collection.setItem(keyValue, value)
@@ -81,7 +81,7 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
} else if funcDef, ok := functor.(*exprFunctor); ok {
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 {
err = self.Errorf("unknown function %s()", rightChild.source())
}
+11 -11
View File
@@ -25,7 +25,7 @@ func evalNot(ctx ExprContext, self *term) (v any, err error) {
return
}
if b, ok := toBool(rightValue); ok {
if b, ok := ToBool(rightValue); ok {
v = !b
} else {
err = self.errIncompatibleType(rightValue)
@@ -48,7 +48,7 @@ func newAndTerm(tk *Token) (inst *term) {
}
func evalAnd(ctx ExprContext, self *term) (v any, err error) {
if isEnabled(ctx, ControlBoolShortcut) {
if CtrlIsEnabled(ctx, ControlBoolShortcut) {
v, err = evalAndWithShortcut(ctx, self)
} else {
v, err = evalAndWithoutShortcut(ctx, self)
@@ -65,8 +65,8 @@ func evalAndWithoutShortcut(ctx ExprContext, self *term) (v any, err error) {
return
}
leftBool, lok = toBool(leftValue)
rightBool, rok = toBool(rightValue)
leftBool, lok = ToBool(leftValue)
rightBool, rok = ToBool(rightValue)
if lok && rok {
v = leftBool && rightBool
@@ -87,13 +87,13 @@ func evalAndWithShortcut(ctx ExprContext, self *term) (v any, err error) {
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)
return
} else if !leftBool {
v = false
} 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
} else {
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) {
if isEnabled(ctx, ControlBoolShortcut) {
if CtrlIsEnabled(ctx, ControlBoolShortcut) {
v, err = evalOrWithShortcut(ctx, self)
} else {
v, err = evalOrWithoutShortcut(ctx, self)
@@ -134,8 +134,8 @@ func evalOrWithoutShortcut(ctx ExprContext, self *term) (v any, err error) {
return
}
leftBool, lok = toBool(leftValue)
rightBool, rok = toBool(rightValue)
leftBool, lok = ToBool(leftValue)
rightBool, rok = ToBool(rightValue)
if lok && rok {
v = leftBool || rightBool
@@ -156,13 +156,13 @@ func evalOrWithShortcut(ctx ExprContext, self *term) (v any, err error) {
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)
return
} else if leftBool {
v = true
} 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
} else {
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) {
count++
} else {
err = self.Errorf("unknown module %q", module)
err = self.Errorf("unknown builtin module %q", module)
break
}
} 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
}
}
+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 {
if functor, ok := rightValue.(Functor); ok {
//ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
ctx.RegisterFunc(leftTerm.source(), functor, typeAny, []ExprFuncParam{
newFuncParamFlag(paramValue, pfOptional|pfRepeat),
ctx.RegisterFunc(leftTerm.source(), functor, TypeAny, []ExprFuncParam{
NewFuncParamFlag(ParamValue, PfDefault|PfRepeat),
})
} else {
v = rightValue
+5 -2
View File
@@ -31,10 +31,13 @@ func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
}
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)
} 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)
for _, key := range keys {
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) {
enable(ctx, control_export_all)
CtrlEnable(ctx, control_export_all)
return
}
+31 -4
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) {
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 {
v = maxValue + v
}
@@ -40,11 +40,14 @@ func verifyRange(indexTerm *term, indexList *ListType, maxValue int) (startIndex
v, _ := ((*indexList)[0]).(*intPair)
startIndex = v.a
endIndex = v.b
if endIndex == ConstLastIndex {
endIndex = maxValue
}
if startIndex < 0 && startIndex >= -maxValue {
startIndex = maxValue + startIndex
}
if endIndex < 0 && endIndex >= -maxValue {
endIndex = maxValue + endIndex + 1
endIndex = maxValue + endIndex
}
if startIndex < 0 || startIndex > maxValue {
err = indexTerm.Errorf("range start-index %d is out of bounds", startIndex)
@@ -87,13 +90,14 @@ func evalIndex(ctx ExprContext, self *term) (v any, err error) {
v = string(unboxedValue[index])
}
case *DictType:
var ok bool
/* var ok bool
var indexValue any
if indexValue, err = verifyKey(indexTerm, indexList); err == nil {
if v, ok = (*unboxedValue)[indexValue]; !ok {
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
}
}
} */
v, err = getDictItem(unboxedValue, indexTerm, indexList, rightValue)
default:
err = self.errIncompatibleTypes(leftValue, rightValue)
}
@@ -113,6 +117,29 @@ func evalIndex(ctx ExprContext, self *term) (v any, err error) {
default:
err = self.errIncompatibleTypes(leftValue, rightValue)
}
} else if IsDict(leftValue) {
d := leftValue.(*DictType)
/* var ok bool
var indexValue any
if indexValue, err = verifyKey(indexTerm, indexList); err == nil {
if v, ok = (*d)[indexValue]; !ok {
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
}
}*/
v, err = getDictItem(d, indexTerm, indexList, rightValue)
}
return
}
func getDictItem(d *DictType, indexTerm *term, indexList *ListType, rightValue any) (v any, err error) {
var ok bool
var indexValue any
if indexValue, err = verifyKey(indexTerm, indexList); err == nil {
if v, ok = (*d)[indexValue]; !ok {
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
}
}
return
}
+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 {
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) {
count, _ := extIt.CallOperation(countName, nil)
v, _ = toInt(count, "")
v, _ = ToInt(count, "")
} else {
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)
}
+3 -3
View File
@@ -12,7 +12,7 @@ type intPair struct {
}
func (p *intPair) TypeName() string {
return typePair
return TypePair
}
func (p *intPair) ToString(opt FmtOpt) string {
@@ -29,7 +29,7 @@ func newRangeTerm(tk *Token) (inst *term) {
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priDot,
priority: priRange,
evalFunc: evalRange,
}
}
@@ -47,7 +47,7 @@ func evalRange(ctx ExprContext, self *term) (v any, err error) {
if leftValue, err = self.children[0].compute(ctx); err != nil {
return
}
rightValue = int64(-1)
rightValue = int64(ConstLastIndex)
} else if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
+1 -1
View File
@@ -70,7 +70,7 @@ func newNotEqualTerm(tk *Token) (inst *term) {
func evalNotEqual(ctx ExprContext, self *term) (v any, err error) {
if v, err = evalEqual(ctx, self); err == nil {
b, _ := toBool(v)
b, _ := ToBool(v)
v = !b
}
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) {
caseData, _ := caseSel.(*selectorCase)
if caseData.filterList == nil {
selectedValue, err = caseData.caseExpr.eval(ctx, false)
selectedValue, err = caseData.caseExpr.Eval(ctx)
match = true
} else if filterList, ok := caseData.filterList.value().([]*term); ok {
if len(filterList) == 0 && exprValue == int64(caseIndex) {
selectedValue, err = caseData.caseExpr.eval(ctx, false)
selectedValue, err = caseData.caseExpr.Eval(ctx)
match = true
} else {
var caseValue any
for _, caseTerm := range filterList {
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
break
}
+15 -6
View File
@@ -11,13 +11,10 @@ import (
//-------- parser
type parser struct {
ctx ExprContext
}
func NewParser(ctx ExprContext) (p *parser) {
p = &parser{
ctx: ctx,
}
func NewParser() (p *parser) {
p = &parser{}
return p
}
@@ -328,6 +325,14 @@ func couldBeACollection(t *term) bool {
return sym == SymList || sym == SymString || sym == SymDict || sym == SymExpression || sym == SymVariable
}
// func areSymbolsOutOfCtx(tk *Token, ctxTerm *term, syms ...Symbol) bool {
// var areOut = false
// if ctxTerm != nil {
// areOut = tk.IsOneOf(syms)
// }
// return areOut
// }
func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
var selectorTerm *term = nil
var currentTerm *term = nil
@@ -335,7 +340,7 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
tree = NewAst()
firstToken := true
// 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); /*&& !areSymbolsOutOfCtx(tk, selectorTerm, SymColon, SymDoubleColon)*/ tk = scanner.Next() {
if tk.Sym == SymComment {
continue
}
@@ -441,6 +446,10 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
} else {
currentTerm, err = tree.addToken2(tk)
}
if tk.IsSymbol(SymColon) {
// Colon outside a selector term acts like a separator
firstToken = true
}
default:
currentTerm, err = tree.addToken2(tk)
}
+107
View File
@@ -0,0 +1,107 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// plugin.go
package expr
import (
"fmt"
"os"
"plugin"
"strings"
)
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 makePluginName(name string) (decorated string) {
var template string
if execName, err := os.Executable(); err != nil || strings.Index(execName, "debug") < 0 {
template = "expr-%s-plugin.so"
} else {
template = "expr-%s-plugin.so.debug"
}
decorated = fmt.Sprintf(template, 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 := makePluginName(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)
}
+9 -4
View File
@@ -178,13 +178,18 @@ func (self *scanner) fetchNextToken() (tk *Token) {
tk = self.makeToken(SymDot, ch)
}
case '\'':
tk = self.makeToken(SymQuote, ch)
if escape {
tk = self.makeToken(SymQuote, ch)
escape = false
} else {
tk = self.fetchString(ch)
}
case '"':
if escape {
tk = self.makeToken(SymDoubleQuote, ch)
escape = false
} else {
tk = self.fetchString()
tk = self.fetchString(ch)
}
case '`':
tk = self.makeToken(SymBackTick, ch)
@@ -533,7 +538,7 @@ func (self *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk
return
}
func (self *scanner) fetchString() (tk *Token) {
func (self *scanner) fetchString(termCh byte) (tk *Token) {
var err error
var ch, prev byte
var sb strings.Builder
@@ -554,7 +559,7 @@ func (self *scanner) fetchString() (tk *Token) {
sb.WriteByte(ch)
}
prev = 0
} else if ch == '"' {
} else if ch == termCh {
break
} else {
prev = ch
+63 -4
View File
@@ -7,10 +7,11 @@ package expr
import (
"fmt"
"slices"
"strings"
// "strings"
)
type SimpleStore struct {
parent ExprContext
varStore map[string]any
funcStore map[string]ExprFunc
}
@@ -26,6 +27,14 @@ func NewSimpleStore() *SimpleStore {
func filterRefName(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 {
return &SimpleStore{
varStore: CloneFilteredMap(ctx.varStore, filterRefName),
@@ -42,10 +51,11 @@ func (ctx *SimpleStore) Merge(src ExprContext) {
}
}
/*
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("vars: {\n")
first := true
for _, name := range ctx.EnumVars(filterPrivName) {
for _, name := range ctx.EnumVars(nil) {
if first {
first = false
} else {
@@ -68,7 +78,7 @@ func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
}
}
sb.WriteString(strings.Repeat("\t", indent))
sb.WriteString("\n}\n")
sb.WriteString("\n}")
}
func varsCtxToString(ctx ExprContext, indent int) string {
@@ -97,21 +107,70 @@ func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteString("\n}\n")
sb.WriteString("\n}")
}
func (ctx *SimpleStore) ToString(opt FmtOpt) string {
var sb strings.Builder
varsCtxToBuilder(&sb, ctx, 0)
sb.WriteByte('\n')
funcsCtxToBuilder(&sb, ctx, 0)
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) {
v, exists = ctx.varStore[varName]
return
}
func (ctx *SimpleStore) GetLast() (v any) {
v = ctx.varStore["last"]
return
}
func (ctx *SimpleStore) UnsafeSetVar(varName string, value any) {
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
ctx.varStore[varName] = value
+2
View File
@@ -101,6 +101,7 @@ const (
SymKwBut
SymKwFunc
SymKwBuiltin
SymKwPlugin
SymKwIn
SymKwInclude
SymKwNil
@@ -113,6 +114,7 @@ func init() {
keywords = map[string]Symbol{
"AND": SymKwAnd,
"BUILTIN": SymKwBuiltin,
"PLUGIN": SymKwPlugin,
"BUT": SymKwBut,
"FUNC": SymKwFunc,
"IN": SymKwIn,
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_func-base_test.go
// t_builtin-base_test.go
package expr
import (
@@ -10,7 +10,7 @@ import (
)
func TestFuncBase(t *testing.T) {
section := "Func-Base"
section := "Builtin-Base"
inputs := []inputType{
/* 1 */ {`isNil(nil)`, 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).
// All rights reserved.
// t_func-math-arith_test.go
// t_builtin-math-arith_test.go
package expr
import (
@@ -10,7 +10,7 @@ import (
)
func TestFuncMathArith(t *testing.T) {
section := "Func-Math-Arith"
section := "Builtin-Math-Arith"
inputs := []inputType{
/* 1 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil},
/* 2 */ {`builtin "math.arith"; add(1,2,3)`, int64(6), nil},
+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"); fileWriteText(handle, "bye-bye"); fileClose(handle)`, true, nil},
/* 6 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); word=fileReadText(handle, "-"); fileClose(handle);word`, "bye", nil},
/* 7 */ {`builtin "os.file"; word=fileReadText(nil, "-")`, nil, errors.New(`fileReadText(): invalid file handle`)},
/* 7 */ {`builtin "os.file"; fileWriteText(nil, "bye")`, nil, errors.New(`fileWriteText(): 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)
}
+3 -2
View File
@@ -31,6 +31,7 @@ func TestDictParser(t *testing.T) {
/* 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`)},
/* 11 */ {`D={"a":1, "b":2}; D["a"]`, int64(1), nil},
}
succeeded := 0
@@ -49,7 +50,7 @@ func TestDictParser(t *testing.T) {
ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc")
ImportMathFuncs(ctx)
parser := NewParser(ctx)
parser := NewParser()
logTest(t, i+1, "Dict", input.source, input.wantResult, input.wantErr)
@@ -110,7 +111,7 @@ func TestDictToStringMultiLine(t *testing.T) {
var good bool
section := "dict-ToString-ML"
want := `{
"first": 1
"first": 1
}`
args := map[any]*term{
"first": newLiteralTerm(NewValueToken(0, 0, SymInteger, "1", 1)),
+1 -1
View File
@@ -14,7 +14,7 @@ func TestExpr(t *testing.T) {
inputs := []inputType{
/* 1 */ {`0?{}`, nil, 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=fileReadText(f); fileClose(f); line`, "uno", nil},
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
/* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil},
/* 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 integer (1)`)},
/* 5 */ {`builtin "string"; ls= ["one", 2, "three"]; joinStr("-", ls)`, nil, errors.New(`joinStr() expected string, got integer (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 integer (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, section, inputs, 19)
parserTest(t, section, inputs)
}
+3 -3
View File
@@ -43,8 +43,8 @@ func dummy(ctx ExprContext, name string, args []any) (result any, err error) {
}
func TestFunctionToStringSimple(t *testing.T) {
source := newGolangFunctor(dummy)
want := "func() {<body>}"
source := NewGolangFunctor(dummy)
want := "func() {}"
got := source.ToString(0)
if got != want {
t.Errorf(`(func() -> result = %v [%T], want = %v [%T]`, got, got, want, want)
@@ -52,7 +52,7 @@ func TestFunctionToStringSimple(t *testing.T) {
}
func TestFunctionGetFunc(t *testing.T) {
source := newGolangFunctor(dummy)
source := NewGolangFunctor(dummy)
want := ExprFunc(nil)
got := source.GetFunc()
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) {
inputs := []inputType{
/* 1 */ {`include "iterator.expr"; it=$(ds,3); ()it`, int64(0), nil},
/* 2 */ {`include "iterator.expr"; it=$(ds,3); it++; it++`, int64(1), nil},
/* 3 */ {`include "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},
/* 5 */ {`builtin "math.arith"; include "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},
/* 7 */ {`builtin "math.arith"; include "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},
/* 10 */ {`include "file-reader.expr"; it=$(ds,"int.list"); it.clean`, true, nil},
/* 1 */ {`include "test-resources/iterator.expr"; it=$(ds,3); ()it`, int64(0), nil},
/* 2 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++`, int64(1), nil},
/* 3 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; #it`, int64(2), nil},
/* 4 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; it.reset; ()it`, int64(0), nil},
/* 5 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); add(it)`, int64(6), nil},
/* 6 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); mul(it)`, int64(0), nil},
/* 7 */ {`builtin "math.arith"; include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); mul(it)`, int64(12000), nil},
/* 8 */ {`include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); it++; it.index`, int64(0), nil},
/* 10 */ {`include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); it.clean`, true, nil},
/* 11 */ {`it=$(1,2,3); it++`, int64(1), nil},
}
// inputs1 := []inputType{
+4 -79
View File
@@ -6,19 +6,12 @@ package expr
import (
"errors"
"strings"
"testing"
)
func TestListParser(t *testing.T) {
section := "List"
/* type inputType struct {
source string
wantResult any
wantErr error
}
*/
inputs := []inputType{
/* 1 */ {`[]`, newListA(), nil},
/* 2 */ {`[1,2,3]`, newListA(int64(1), int64(2), int64(3)), nil},
@@ -56,82 +49,14 @@ func TestListParser(t *testing.T) {
/* 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},
// /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil},
/* 38 */ {`[0,1,2,3,4][2:3]`, newListA(int64(2)), nil},
/* 39 */ {`[0,1,2,3,4][3:-1]`, newListA(int64(3)), nil},
/* 40 */ {`[0,1,2,3,4][-3:-1]`, newListA(int64(2), int64(3)), nil},
/* 41 */ {`[0,1,2,3,4][0:]`, newListA(int64(0), int64(1), int64(2), int64(3), int64(4)), nil},
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 17)
parserTest(t, section, inputs)
return
succeeded := 0
failed := 0
// inputs1 := []inputType{
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
// }
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"
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)
return true
})
+2 -2
View File
@@ -188,7 +188,7 @@ func doTest(t *testing.T, section string, input *inputType, count int) (good boo
var gotErr error
ctx := NewSimpleStore()
parser := NewParser(ctx)
parser := NewParser()
logTest(t, count, section, input.source, input.wantResult, input.wantErr)
@@ -203,7 +203,7 @@ func doTest(t *testing.T, section string, input *inputType, count int) (good boo
eq := reflect.DeepEqual(gotResult, input.wantResult)
if !eq /*gotResult != input.wantResult*/ {
t.Errorf("%d: %q -> result = %v [%s], want = %v [%s]", count, input.source, gotResult, typeName(gotResult), input.wantResult, typeName(input.wantResult))
t.Errorf("%d: %q -> result = %v [%s], want = %v [%s]", count, input.source, gotResult, TypeName(gotResult), input.wantResult, TypeName(input.wantResult))
good = false
}
+1 -1
View File
@@ -45,7 +45,7 @@ func TestScanner(t *testing.T) {
/* 22 */ {"@", SymAt, nil, nil},
/* 23 */ {`#`, SymHash, nil, nil},
/* 24 */ {`%`, SymPercent, nil, nil},
/* 25 */ {`'`, SymQuote, nil, nil},
/* 25 */ {`\'`, SymQuote, nil, nil},
/* 26 */ {`\"`, SymDoubleQuote, nil, nil},
/* 27 */ {`_`, SymUndescore, nil, nil},
/* 28 */ {`<>`, SymLessGreater, nil, nil},
+5 -5
View File
@@ -63,7 +63,7 @@ func TestIsString(t *testing.T) {
}
count++
b, ok := toBool(true)
b, ok := ToBool(true)
if !(ok && b) {
t.Errorf("%d: toBool(true) b=%v, ok=%v -> result = false, want true", count, b, ok)
failed++
@@ -72,7 +72,7 @@ func TestIsString(t *testing.T) {
}
count++
b, ok = toBool("abc")
b, ok = ToBool("abc")
if !(ok && b) {
t.Errorf("%d: toBool(\"abc\") b=%v, ok=%v -> result = false, want true", count, b, ok)
failed++
@@ -81,7 +81,7 @@ func TestIsString(t *testing.T) {
}
count++
b, ok = toBool([]int{})
b, ok = ToBool([]int{})
if ok {
t.Errorf("%d: toBool([]) b=%v, ok=%v -> result = true, want false", count, b, ok)
failed++
@@ -97,7 +97,7 @@ func TestToIntOk(t *testing.T) {
wantValue := int(64)
wantErr := error(nil)
gotValue, gotErr := toInt(source, "test")
gotValue, gotErr := ToInt(source, "test")
if gotErr != nil || gotValue != wantValue {
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
@@ -110,7 +110,7 @@ func TestToIntErr(t *testing.T) {
wantValue := 0
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 {
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
+4 -4
View File
@@ -156,15 +156,15 @@ func (self *term) toInt(computedValue any, valueDescription string) (i int, err
if index64, ok := computedValue.(int64); ok {
i = int(index64)
} else {
err = self.Errorf("%s, got %s (%v)", valueDescription, typeName(computedValue), computedValue)
err = self.Errorf("%s, got %s (%v)", valueDescription, TypeName(computedValue), computedValue)
}
return
}
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
leftType := typeName(leftValue)
leftType := TypeName(leftValue)
leftText := getFormatted(leftValue, Truncate)
rightType := typeName(rightValue)
rightType := TypeName(rightValue)
rightText := getFormatted(rightValue, Truncate)
return self.tk.Errorf(
"left operand '%s' [%s] and right operand '%s' [%s] are not compatible with operator %q",
@@ -176,7 +176,7 @@ func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
func (self *term) errIncompatibleType(value any) error {
return self.tk.Errorf(
"prefix/postfix operator %q do not support operand '%v' [%s]",
self.source(), value, typeName(value))
self.source(), value, TypeName(value))
}
func (self *term) Errorf(template string, args ...any) (err error) {
+32
View File
@@ -0,0 +1,32 @@
builtin ["os.file", "base"];
readInt=func(fh){
line=fileReadText(fh);
line ? [nil] {nil} :: {int(line)}
};
ds={
"init":func(filename){
fh=fileOpen(filename);
fh ? [nil] {nil} :: { @current=readInt(fh) };
fh
},
"current":func(){
current
},
"next":func(fh){
@current=readInt(fh);
current
},
"clean":func(fh){
fileClose(fh)
}
}
//;f=$(ds, "int.list")
//;f++
//;f++
//;f++
//*/
//;add(f)
+5 -1
View File
@@ -60,7 +60,11 @@ func (tk *Token) IsError() bool {
}
func (tk *Token) IsTerm(termSymbols []Symbol) bool {
return tk.IsEos() || tk.IsError() || (termSymbols != nil && slices.Index(termSymbols, tk.Sym) >= 0)
return tk.IsEos() || tk.IsError() || tk.IsOneOf(termSymbols)
}
func (tk *Token) IsOneOf(termSymbols []Symbol) bool {
return termSymbols != nil && slices.Index(termSymbols, tk.Sym) >= 0
}
func (tk *Token) IsSymbol(sym Symbol) bool {
+3 -3
View File
@@ -86,7 +86,7 @@ func numAsFloat(v any) (f float64) {
return
}
func toBool(v any) (b bool, ok bool) {
func ToBool(v any) (b bool, ok bool) {
ok = true
switch x := v.(type) {
case string:
@@ -201,13 +201,13 @@ func CloneFilteredMap[K comparable, V any](source map[K]V, filter func(key K) (a
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 {
i = int(valueInt64)
} else if valueInt, ok := value.(int); ok {
i = valueInt
} else {
err = fmt.Errorf("%s expected integer, got %s (%v)", description, typeName(value), value)
err = fmt.Errorf("%s expected integer, got %s (%v)", description, TypeName(value), value)
}
return
}