Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fe5c8e9619 | |||
| 7164e8816c | |||
| e0f3b028fc | |||
| 522b5983bd | |||
| f1e2163277 | |||
| bbdf498cf3 | |||
| f75c991ed2 | |||
| a7836143cc | |||
| ba9b9cb28f | |||
| ef1baa11a8 | |||
| cfddbd60b9 | |||
| 0760141caf | |||
| b9e780e659 | |||
| e41ddc664c | |||
| 3b641ac793 | |||
| a1a62b6794 | |||
| a18d92534f | |||
| ec0963e26f | |||
| be992131b1 | |||
| 0e3960321f | |||
| 61d34fef7d | |||
| 581f1585e6 | |||
| 531cb1c249 | |||
| d1b468f35b | |||
| ff9cf80c66 | |||
| d63b18fd76 | |||
| 019470faf1 | |||
| 302430d57d | |||
| 62ef0d699d | |||
| 866de759dd | |||
| b1d6b6de44 | |||
| 7e357eea62 | |||
| d6b4c79736 | |||
| d066344af8 | |||
| f41dba069e | |||
| 703ecf6829 | |||
| ba479a1b99 | |||
| 24e6a293b0 | |||
| 28f464c4dc | |||
| 9fb611aa20 | |||
| 56d6d06d15 | |||
| d9f7e5b1ad | |||
| 0f54e01ef3 | |||
| 63f5db00b3 | |||
| 9745a5d909 | |||
| 0bb4c96481 | |||
| 1757298eb4 | |||
| 54041552d4 | |||
| 5302907dcf | |||
| eb4b17f078 | |||
| 33d70d6d1a | |||
| 9df9ad5dd1 | |||
| 34dc828ead | |||
| 29bc2c62a3 | |||
| 8eb2d77ea3 | |||
| 53bcf90d2a | |||
| f347b15146 | |||
| 115ce26ce9 | |||
| 227944b3fb | |||
| 0d01afcc9f | |||
| 00c76b41f1 | |||
| 08e0979cdd | |||
| f04f5822ec | |||
| 80d47879e9 | |||
| 985eb3d19d | |||
| 45734ab393 | |||
| c100cf349d | |||
| 8144122d2c | |||
| 188ea354ee | |||
| 2fc6bbfe10 | |||
| 847d85605e | |||
| 9c29392389 | |||
| a7b6e6f8d2 | |||
| a16ac70e4a | |||
| ab2e3f0528 | |||
| 974835a8ef | |||
| 457a656073 | |||
| 9e63e1402e | |||
| e4ded4f746 | |||
| 4e3af837e6 | |||
| ca12722c93 | |||
| d96123ab02 | |||
| f2d6d63017 | |||
| 905b2af7fa | |||
| 9307473d08 | |||
| 10a596a4cd | |||
| 609fb21505 | |||
| 7650a4a441 | |||
| f51d6023ae | |||
| 99454227d5 | |||
| 75358e5d35 | |||
| 51b272dda8 | |||
| 7f282e5460 | |||
| cff21b40f7 | |||
| 4f432da2b9 | |||
| e4b4b4fb79 | |||
| c04678c426 |
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// 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 {
|
||||
var defCtx ExprContext
|
||||
if ctx != nil {
|
||||
// if ctx.GetParent() != nil {
|
||||
// defCtx = ctx.Clone()
|
||||
// defCtx.SetParent(ctx)
|
||||
// } else {
|
||||
defCtx = ctx
|
||||
// }
|
||||
}
|
||||
return &exprFunctor{expr: e, params: params, defCtx: defCtx}
|
||||
}
|
||||
|
||||
func (functor *exprFunctor) GetDefinitionContext() ExprContext {
|
||||
return functor.defCtx
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// func-builtins.go
|
||||
// builtin-base.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
@@ -67,7 +68,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 +91,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 +116,33 @@ 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
|
||||
}
|
||||
|
||||
func stringFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
switch v := args[0].(type) {
|
||||
case int64:
|
||||
result = strconv.FormatInt(v, 10)
|
||||
case float64:
|
||||
result = strconv.FormatFloat(v, 'g', -1, 64)
|
||||
case bool:
|
||||
if v {
|
||||
result = "true"
|
||||
} else {
|
||||
result = "false"
|
||||
}
|
||||
case string:
|
||||
result = v
|
||||
case *FractionType:
|
||||
result = v.ToString(0)
|
||||
case Formatter:
|
||||
result = v.ToString(0)
|
||||
case fmt.Stringer:
|
||||
result = v.String()
|
||||
default:
|
||||
err = ErrCantConvert(name, v, "string")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -127,9 +154,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 +175,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 +186,29 @@ 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("string", NewGolangFunctor(stringFunc), TypeString, 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.")
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// builtin-fmt.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func getStdout(ctx ExprContext) io.Writer {
|
||||
var w io.Writer
|
||||
if wany, exists := ctx.GetVar(ControlStdout); exists && wany != nil {
|
||||
w, _ = wany.(io.Writer)
|
||||
}
|
||||
if w == nil {
|
||||
w = os.Stdout
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func printFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var n int
|
||||
if n, err = fmt.Fprint(getStdout(ctx), args...); err == nil {
|
||||
result = int64(n)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func printLnFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var n int
|
||||
if n, err = fmt.Fprintln(getStdout(ctx), args...); err == nil {
|
||||
result = int64(n)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ImportFmtFuncs(ctx ExprContext) {
|
||||
ctx.RegisterFunc("print", NewGolangFunctor(printFunc), TypeInt, []ExprFuncParam{
|
||||
NewFuncParamFlag(ParamItem, PfRepeat),
|
||||
})
|
||||
ctx.RegisterFunc("println", NewGolangFunctor(printLnFunc), TypeInt, []ExprFuncParam{
|
||||
NewFuncParamFlag(ParamItem, PfRepeat),
|
||||
})
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterBuiltinModule("fmt", ImportFmtFuncs, "String and console formatting functions")
|
||||
}
|
||||
@@ -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()")
|
||||
}
|
||||
@@ -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()")
|
||||
}
|
||||
@@ -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(typeFilepath),
|
||||
ctx.RegisterFunc("fileOpen", NewGolangFunctor(openFileFunc), TypeHandle, []ExprFuncParam{
|
||||
NewFuncParam(ParamFilepath),
|
||||
})
|
||||
ctx.RegisterFunc("appendFile", newGolangFunctor(appendFileFunc), typeHandle, []ExprFuncParam{
|
||||
newFuncParam(typeFilepath),
|
||||
|
||||
ctx.RegisterFunc("fileAppend", NewGolangFunctor(appendFileFunc), TypeHandle, []ExprFuncParam{
|
||||
NewFuncParam(ParamFilepath),
|
||||
})
|
||||
ctx.RegisterFunc("createFile", newGolangFunctor(createFileFunc), typeHandle, []ExprFuncParam{
|
||||
newFuncParam(typeFilepath),
|
||||
|
||||
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")
|
||||
}
|
||||
@@ -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 = ToGoInt(args[1], name+"()"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if count, err = toInt(args[2], name+"()"); err != nil {
|
||||
if count, err = ToGoInt(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,40 +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),
|
||||
newFuncParamFlagDef(paramItem, pfOptional|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),
|
||||
newFuncParamFlag(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),
|
||||
newFuncParamFlag(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")
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+16
-24
@@ -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,47 +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 {
|
||||
if typer, ok := value.(Typer); ok {
|
||||
return fmt.Errorf("%s(): can't convert %s to %s", funcName, typer.TypeName(), kind)
|
||||
} else {
|
||||
return fmt.Errorf("%s(): can't convert %T to %s", funcName, value, kind)
|
||||
}
|
||||
func 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 %T (%v)", funcName, kind, 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 {
|
||||
return fmt.Errorf("division by zero")
|
||||
}
|
||||
// func ErrDivisionByZero() error {
|
||||
// return fmt.Errorf("division by zero")
|
||||
// }
|
||||
|
||||
// --- Parameter errors
|
||||
|
||||
// func errOneParam(funcName string) error {
|
||||
// return fmt.Errorf("%s() requires exactly one param", funcName)
|
||||
// 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 %T (%v) for parameter %q", funcName, 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 %T (%v)", funcName, paramName, paramType, 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)
|
||||
}
|
||||
|
||||
+18
-21
@@ -5,26 +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 = "..."
|
||||
typeFilepath = "filepath"
|
||||
typeDirpath = "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"
|
||||
)
|
||||
|
||||
// const (
|
||||
// typeInteger = "int"
|
||||
// typeFloat = "float"
|
||||
// typeString = "string"
|
||||
// typeFraction = "fract"
|
||||
// typeList = "list"
|
||||
// typeDict = "dict"
|
||||
// )
|
||||
// to be moved in its own source file
|
||||
const (
|
||||
ConstLastIndex = 0xFFFF_FFFF
|
||||
)
|
||||
+12
-10
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+8
-2
@@ -10,12 +10,14 @@ type Functor interface {
|
||||
SetFunc(info ExprFunc)
|
||||
GetFunc() ExprFunc
|
||||
GetParams() []ExprFuncParam
|
||||
GetDefinitionContext() ExprContext
|
||||
}
|
||||
|
||||
// ---- Function Param Info
|
||||
type ExprFuncParam interface {
|
||||
Name() string
|
||||
Type() string
|
||||
IsDefault() bool
|
||||
IsOptional() bool
|
||||
IsRepeat() bool
|
||||
DefaultValue() any
|
||||
@@ -30,20 +32,24 @@ type ExprFunc interface {
|
||||
Functor() Functor
|
||||
Params() []ExprFuncParam
|
||||
ReturnType() string
|
||||
PrepareCall(parentCtx ExprContext, name string, varParams *[]any) (ctx ExprContext, err error)
|
||||
AllocContext(parentCtx ExprContext) (ctx ExprContext)
|
||||
}
|
||||
|
||||
// ----Expression Context
|
||||
type ExprContext interface {
|
||||
Clone() ExprContext
|
||||
Merge(ctx 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)
|
||||
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
|
||||
GetFuncInfo(name string) (item ExprFunc, exists bool)
|
||||
Call(name string, args []any) (result any, err error)
|
||||
// RegisterFunc(name string, f Functor, minArgs, maxArgs int)
|
||||
RegisterFuncInfo(info ExprFunc)
|
||||
RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) error
|
||||
}
|
||||
|
||||
+18
-40
@@ -4,13 +4,14 @@
|
||||
// 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"
|
||||
ControlStdout = "_stdout"
|
||||
)
|
||||
|
||||
// Other control variables
|
||||
@@ -20,43 +21,20 @@ 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 SetCtrl(ctx ExprContext, name string, value any) (current any) {
|
||||
current, _ = ctx.GetVar(name)
|
||||
ctx.UnsafeSetVar(name, value)
|
||||
return
|
||||
}
|
||||
|
||||
func initDefaultVars(ctx ExprContext) {
|
||||
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)
|
||||
if _, exists := ctx.GetVar(ControlPreset); exists {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
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.UnsafeSetVar(ControlPreset, true)
|
||||
ctx.UnsafeSetVar(ControlBoolShortcut, true)
|
||||
ctx.UnsafeSetVar(ControlSearchPath, init_search_path)
|
||||
}
|
||||
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// dict-type.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DictType map[any]any
|
||||
|
||||
func MakeDict() (dict *DictType) {
|
||||
d := make(DictType)
|
||||
dict = &d
|
||||
return
|
||||
}
|
||||
|
||||
func newDict(dictAny map[any]*term) (dict *DictType) {
|
||||
var d DictType
|
||||
if dictAny != nil {
|
||||
d = make(DictType, len(dictAny))
|
||||
for i, item := range dictAny {
|
||||
d[i] = item
|
||||
}
|
||||
} else {
|
||||
d = make(DictType)
|
||||
}
|
||||
dict = &d
|
||||
return
|
||||
}
|
||||
|
||||
func (dict *DictType) toMultiLine(sb *strings.Builder, opt FmtOpt) {
|
||||
indent := GetFormatIndent(opt)
|
||||
flags := GetFormatFlags(opt)
|
||||
//sb.WriteString(strings.Repeat(" ", indent))
|
||||
sb.WriteByte('{')
|
||||
|
||||
if len(*dict) > 0 {
|
||||
innerOpt := MakeFormatOptions(flags, indent+1)
|
||||
nest := strings.Repeat(" ", indent+1)
|
||||
sb.WriteByte('\n')
|
||||
|
||||
first := true
|
||||
for name, value := range *dict {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
sb.WriteByte(',')
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
|
||||
sb.WriteString(nest)
|
||||
if key, ok := name.(string); ok {
|
||||
sb.WriteString(string('"') + key + string('"'))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", name))
|
||||
}
|
||||
sb.WriteString(": ")
|
||||
if f, ok := value.(Formatter); ok {
|
||||
sb.WriteString(f.ToString(innerOpt))
|
||||
} else if _, ok = value.(Functor); ok {
|
||||
sb.WriteString("func(){}")
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", value))
|
||||
}
|
||||
}
|
||||
sb.WriteByte('\n')
|
||||
sb.WriteString(strings.Repeat(" ", indent))
|
||||
}
|
||||
sb.WriteString("}")
|
||||
}
|
||||
|
||||
func (dict *DictType) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
flags := GetFormatFlags(opt)
|
||||
if flags&MultiLine != 0 {
|
||||
dict.toMultiLine(&sb, opt)
|
||||
} else {
|
||||
sb.WriteByte('{')
|
||||
first := true
|
||||
for key, value := range *dict {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
if s, ok := key.(string); ok {
|
||||
sb.WriteString(string('"') + s + string('"'))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", key))
|
||||
}
|
||||
sb.WriteString(": ")
|
||||
if formatter, ok := value.(Formatter); ok {
|
||||
sb.WriteString(formatter.ToString(opt))
|
||||
} else if t, ok := value.(*term); ok {
|
||||
sb.WriteString(t.String())
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%#v", value))
|
||||
}
|
||||
}
|
||||
sb.WriteByte('}')
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (dict *DictType) String() string {
|
||||
return dict.ToString(0)
|
||||
}
|
||||
|
||||
func (dict *DictType) TypeName() string {
|
||||
return "dict"
|
||||
}
|
||||
|
||||
func (dict *DictType) hasKey(target any) (ok bool) {
|
||||
for key := range *dict {
|
||||
if ok = reflect.DeepEqual(key, target); ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (dict *DictType) clone() (c *DictType) {
|
||||
c = newDict(nil)
|
||||
for k, v := range *dict {
|
||||
(*c)[k] = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (dict *DictType) merge(second *DictType) {
|
||||
if second != nil {
|
||||
for k, v := range *second {
|
||||
(*dict)[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dict *DictType) setItem(key any, value any) (err error) {
|
||||
(*dict)[key] = value
|
||||
return
|
||||
}
|
||||
|
||||
////////////////
|
||||
|
||||
type DictFormat interface {
|
||||
ToDict() *DictType
|
||||
}
|
||||
+289
-139
@@ -9,6 +9,7 @@ Expressions calculator
|
||||
:icons: font
|
||||
:icon-set: fi
|
||||
:numbered:
|
||||
:data-uri:
|
||||
//:table-caption: Tabella
|
||||
//:figure-caption: Diagramma
|
||||
:docinfo1:
|
||||
@@ -19,21 +20,52 @@ Expressions calculator
|
||||
:rouge-style: gruvbox
|
||||
// :rouge-style: colorful
|
||||
//:rouge-style: monokay
|
||||
// Work around to manage double-column in back-tick quotes
|
||||
:2c: ::
|
||||
|
||||
toc::[]
|
||||
|
||||
#TODO: Work in progress (last update on 2024/05/20, 06:58 a.m.)#
|
||||
#TODO: Work in progress (last update on 2024/06/21, 05:40 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.
|
||||
Before we begin to describe the syntax of _Expr_, it is worth introducing _dev-expr_ because it will be used to show many examples of expressions.
|
||||
|
||||
`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 +79,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 +92,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,10 +102,11 @@ 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
|
||||
>>>
|
||||
----
|
||||
|
||||
<1> Only available for single fraction values
|
||||
@@ -83,8 +116,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 +134,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:
|
||||
@@ -155,7 +188,7 @@ Value range: *-9223372036854775808* to *9223372036854775807*
|
||||
| [blue]`*` | _product_ | Multiply two values | [blue]`-1 * 2` -> -2
|
||||
| [blue]`/` | _Division_ | Divide the left value by the right one^(*)^ | [blue]`-10 / 2` -> 5
|
||||
| [blue]`%` | _Modulo_ | Remainder of the integer division | [blue]`5 % 2` -> 1
|
||||
|===
|
||||
|===
|
||||
|
||||
^(*)^ See also the _float division_ [blue]`./` below.
|
||||
|
||||
@@ -173,15 +206,20 @@ _dec-seq_ = _see-integer-literal-syntax_
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`1.0` +
|
||||
[green]`1` +
|
||||
[green]`1`
|
||||
|
||||
`>>>` [blue]`0.123` +
|
||||
[green]`0.123` +
|
||||
[green]`0.123`
|
||||
|
||||
`>>>` [blue]`4.5e+3` +
|
||||
[green]`4500` +
|
||||
[green]`4500`
|
||||
|
||||
`>>>` [blue]`4.5E-33` +
|
||||
[green]`4.5e-33` +
|
||||
[green]`4.5e-33`
|
||||
|
||||
`>>>` [blue]`4.5E-3` +
|
||||
[green]`0.0045` +
|
||||
[green]`0.0045`
|
||||
|
||||
`>>>` [blue]`4.5E10` +
|
||||
[green]`4.5e+10`
|
||||
|
||||
@@ -211,35 +249,43 @@ _digit-seq_ = _see-integer-literal-syntax_
|
||||
====
|
||||
|
||||
.Examples
|
||||
// [source,go]
|
||||
// ----
|
||||
`>>>` [blue]`1 | 2` +
|
||||
[green]`1|2` +
|
||||
[green]`1|2`
|
||||
|
||||
`>>>` [blue]`4|6` [gray]_// Fractions are always reduced to their lowest terms_ +
|
||||
[green]`2|3` +
|
||||
[green]`2|3`
|
||||
|
||||
`>>>` [blue]`1|2 + 2|3` +
|
||||
[green]`7|6` +
|
||||
[green]`7|6`
|
||||
|
||||
`>>>` [blue]`1|2 * 2|3` +
|
||||
[green]`1|3` +
|
||||
[green]`1|3`
|
||||
|
||||
`>>>` [blue]`1|2 / 1|3` +
|
||||
[green]`3|2` +
|
||||
[green]`3|2`
|
||||
|
||||
`>>>` [blue]`1|2 ./ 1|3` [gray]_// Force decimal division_ +
|
||||
[green]`1.5` +
|
||||
[green]`1.5`
|
||||
|
||||
`>>>` [blue]`-1|2` +
|
||||
[green]`-1|2` +
|
||||
[green]`-1|2`
|
||||
|
||||
`>>>` [blue]`1|-2` [gray]_// Invalid sign specification_ +
|
||||
[red]_Eval Error: [1:3] infix operator "|" requires two non-nil operands, got 1_ +
|
||||
[red]_Eval Error: [1:3] infix operator "|" requires two non-nil operands, got 1_
|
||||
|
||||
`>>>` [blue]`1|(-2)` +
|
||||
[green]`-1|2`
|
||||
// ----
|
||||
|
||||
|
||||
Fractions can be used together with integers and floats in expressions.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`1|2 + 5` +
|
||||
[green]`11|2` +
|
||||
[green]`11|2`
|
||||
|
||||
`>>>` [blue]`4 - 1|2` +
|
||||
[green]`7|2` +
|
||||
[green]`7|2`
|
||||
|
||||
`>>>` [blue]`1.0 + 1|2` +
|
||||
[green]`1.5`
|
||||
|
||||
@@ -250,12 +296,15 @@ Strings are character sequences enclosed between two double quote [blue]`"`.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`"I'm a string"` +
|
||||
[green]`I'm a string` +
|
||||
[green]`I'm a string`
|
||||
|
||||
`>>>` [blue]`"123abc?!"` +
|
||||
[green]`123abc?!` +
|
||||
[green]`123abc?!`
|
||||
|
||||
`>>>` [blue]`"123\nabc"` +
|
||||
[green]`123` +
|
||||
[green]`abc` +
|
||||
[green]`abc`
|
||||
|
||||
`>>>` [blue]`"123\tabc"` +
|
||||
[green]`123{nbsp}{nbsp}{nbsp}{nbsp}abc`
|
||||
|
||||
@@ -272,25 +321,36 @@ 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_ +
|
||||
[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.
|
||||
@@ -334,14 +394,16 @@ Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relationa
|
||||
|
||||
[CAUTION]
|
||||
====
|
||||
Currently, boolean operations are evaluated using _short cut evaluation_. This means that, if the left expression of the [blue]`and` and [blue]`or` operators is sufficient to establish the result of the whole operation, the right expression would not evaluated at all.
|
||||
|
||||
Currently, boolean operations are evaluated using _short cut evaluation_. This means that, if the left expression of the [blue]`and` and [blue]`or` operators is sufficient to establish the result of the whole operation, the right expression would not be evaluated at all.
|
||||
.Example
|
||||
[source,go]
|
||||
----
|
||||
2 > (a=1) or (a=8) > 0; a // <1>
|
||||
----
|
||||
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and as a conseguence its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
|
||||
|
||||
|
||||
TIP: `dev-expr` provides the _ctrl()_ function that allows to change this behaviour.
|
||||
====
|
||||
|
||||
=== Lists
|
||||
@@ -356,13 +418,17 @@ _non-empty-list_ = "**[**" _any-value_ {"**,**" _any-value} "**]**" +
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`[1,2,3]` [gray]_// List of integers_ +
|
||||
[green]`[1, 2, 3]` +
|
||||
[green]`[1, 2, 3]`
|
||||
|
||||
`>>>` [blue]`["one", "two", "three"]` [gray]_// List of strings_ +
|
||||
[green]`["one", "two", "three"]` +
|
||||
[green]`["one", "two", "three"]`
|
||||
|
||||
`>>>` [blue]`["one", 2, false, 4.1]` [gray]_// List of mixed-types_ +
|
||||
[green]`["one", 2, false, 4.1]` +
|
||||
[green]`["one", 2, false, 4.1]`
|
||||
|
||||
`>>>` [blue]`["one"+1, 2.0*(9-2)]` [gray]_// List of expressions_ +
|
||||
[green]`["one1", 14]` +
|
||||
[green]`["one1", 14]`
|
||||
|
||||
`>>>` [blue]`[ [1,"one"], [2,"two"]]` [gray]_// List of lists_ +
|
||||
[green]`[[1, "one"], [2, "two"]]`
|
||||
|
||||
@@ -375,37 +441,52 @@ _non-empty-list_ = "**[**" _any-value_ {"**,**" _any-value} "**]**" +
|
||||
| [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` -> _[1,3]_
|
||||
| [blue]`>>` | _Front insertion_ | Insert an item in front | [blue]`0 >> [1,2]` -> _[0,1,2]_
|
||||
| [blue]`<<` | _Back insertion_ | Insert an item at end | [blue]`[1,2] << 3` -> _[1,2,3]_
|
||||
| [blue]`.` | _List item_ | Item at given position | [blue]`[1,2.3].1` -> _2_
|
||||
| [blue]`[]` | _Item at index_ | Item at given position | [blue]`[1,2,3][1]` -> _2_
|
||||
| [blue]`in` | _Item in list_ | True if item is in list | [blue]`2 in [1,2,3]` -> _true_ +
|
||||
[blue]`6 in [1,2,3]` -> _false_
|
||||
|===
|
||||
|
||||
The items of array can be accessed using the dot `.` operator.
|
||||
Array's items can be accessed using the index `[]` operator.
|
||||
|
||||
.Item access syntax
|
||||
====
|
||||
_item_ = _list-expr_ "**.**" _integer-expr_
|
||||
*_item_* = _list-expr_ "**[**" _integer-expr_ "**]**"
|
||||
====
|
||||
|
||||
.Sub-array (or slice of array) syntax
|
||||
====
|
||||
*_slice_* = _string-expr_ "**[**" _integer-expr_ "**:**" _integer-expr_ "**]**"
|
||||
====
|
||||
|
||||
.Items of list
|
||||
`>>>` [blue]`[1,2,3].1` +
|
||||
[green]`2` +
|
||||
[green]`2`
|
||||
|
||||
`>>>` [blue]`list=[1,2,3]; list.1` +
|
||||
[green]`2` +
|
||||
[green]`2`
|
||||
|
||||
`>>>` [blue]`["one","two","three"].1` +
|
||||
[green]`two` +
|
||||
[green]`two`
|
||||
|
||||
`>>>` [blue]`list=["one","two","three"]; list.(2-1)` +
|
||||
[green]`two` +
|
||||
[green]`two`
|
||||
|
||||
`>>>` [blue]`list.(-1)` +
|
||||
[green]`three` +
|
||||
[green]`three`
|
||||
|
||||
`>>>` [blue]`list.(10)` +
|
||||
[red]`Eval Error: [1:9] index 10 out of bounds` +
|
||||
[red]`Eval Error: [1:9] index 10 out of bounds`
|
||||
|
||||
`>>>` [blue]`#list` +
|
||||
[green]`3` +
|
||||
`>>>` [blue]`index=2; ["a", "b", "c", "d"].index` +
|
||||
[green]`3`
|
||||
|
||||
`>>>` [blue]`index=2; ["a", "b", "c", "d"][index]` +
|
||||
[green]`c`
|
||||
|
||||
|
||||
`>>>` [blue]`["a", "b", "c", "d"][2:]` +
|
||||
[green]`["c", "d"]`
|
||||
|
||||
|
||||
=== Dictionaries
|
||||
The _dictionary_, or _dict_, data-type is set of pairs _key/value_. It is also known as _map_ or _associative array_.
|
||||
@@ -419,14 +500,11 @@ _empty-dict_ = "**{}**" +
|
||||
_non-empty-dict_ = "**{**" _key-scalar_ "**:**" _any-value_ {"**,**" _key-scalar_ "**:**" _any-value} "**}**" +
|
||||
====
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`{1:"one", 2:"two"}` +
|
||||
[green]`{1: "one", 2: "two"}` +
|
||||
`>>>` [blue]`{"one":1, "two": 2}` +
|
||||
[green]`{"one": 1, "two": 2}` +
|
||||
`>>>` [blue]`{"sum":1+2+3, "prod":1*2*3}` +
|
||||
[green]`{"sum": 6, "prod": 6}`
|
||||
|
||||
.Item access syntax
|
||||
====
|
||||
*_item_* = _dict-expr_ "**[**" _key-expr_ "**]**"
|
||||
====
|
||||
|
||||
.Dict operators
|
||||
[cols="^2,^2,4,5"]
|
||||
@@ -434,11 +512,27 @@ _non-empty-dict_ = "**{**" _key-scalar_ "**:**" _any-value_ {"**,**" _key-scalar
|
||||
| Symbol | Operation | Description | Examples
|
||||
|
||||
| [blue]`+` | _Join_ | Joins two dicts | [blue]`{1:"one"}+{6:"six"}` -> _{1: "one", 6: "six"}_
|
||||
| [blue]`.` | _Dict item value_ | Item value of given key | [blue]`{"one":1, "two":2}."two"` -> _2_
|
||||
| [blue]`[]` | _Dict item value_ | Item value of given key | [blue]`{"one":1, "two":2}["two"]` -> _2_
|
||||
| [blue]`in` | _Key in dict_ | True if key is in dict | [blue]`"one" in {"one":1, "two":2}` -> _true_ +
|
||||
[blue]`"six" in {"one":1, "two":2}` -> _false_
|
||||
|===
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`{1:"one", 2:"two"}` +
|
||||
[green]`{1: "one", 2: "two"}`
|
||||
|
||||
`>>>` [blue]`{"one":1, "two": 2}` +
|
||||
[green]`{"one": 1, "two": 2}`
|
||||
|
||||
`>>>` [blue]`{"sum":1+2+3, "prod":1*2*3}` +
|
||||
[green]`{"sum": 6, "prod": 6}`
|
||||
|
||||
`>>>` [blue]`{"one":1, "two":2}["two"]` +
|
||||
[green]`2`
|
||||
|
||||
`>>>` [blue]`d={"one":1, "two":2}; d["six"]=6; d` +
|
||||
[green]`{"two": 2, "one": 1, "six": 6}`
|
||||
|
||||
|
||||
== Variables
|
||||
_Expr_, like most programming languages, supports variables. A variable is an identifier with an assigned value. Variables are stored in _contexts_.
|
||||
@@ -454,17 +548,23 @@ NOTE: The assign operator [blue]`=` returns the value assigned to the variable.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`a=1` +
|
||||
[green]`1` +
|
||||
[green]`1`
|
||||
|
||||
`>>>` [blue]`a_b=1+2` +
|
||||
[green]`1+2` +
|
||||
[green]`1+2`
|
||||
|
||||
`>>>` [blue]`a_b` +
|
||||
[green]`3` +
|
||||
`>>>` [blue]`x = 5.2 * (9-3)` [gray]_// The assigned value has the approximation error typical of the float data-type_ +
|
||||
[green]`31.200000000000003` +
|
||||
[green]`3`
|
||||
|
||||
`>>>` [blue]`x = 5.2 * (9-3)` [gray]_// The assigned value has the typical approximation error of the float data-type_ +
|
||||
[green]`31.200000000000003`
|
||||
|
||||
`>>>` [blue]`x = 1; y = 2*x` +
|
||||
[green]`2` +
|
||||
[green]`2`
|
||||
|
||||
`>>>` [blue]`_a=2` +
|
||||
[red]`Parse Error: [1:2] unexpected token "_"` +
|
||||
[red]`Parse Error: [1:2] unexpected token "_"`
|
||||
|
||||
`>>>` [blue]`1=2` +
|
||||
[red]`Parse Error: assign operator ("=") must be preceded by a variable`
|
||||
|
||||
@@ -474,6 +574,11 @@ NOTE: The assign operator [blue]`=` returns the value assigned to the variable.
|
||||
=== [blue]`;` operator
|
||||
The semicolon operator [blue]`;` is an infixed pseudo-operator. It evaluates the left expression first and then the right expression. The value of the latter is the final result.
|
||||
|
||||
.Mult-expression syntax
|
||||
====
|
||||
*_multi-expression_* = _expression_ {"**;**" _expression_ }
|
||||
====
|
||||
|
||||
An expression that contains [blue]`;` is called a _multi-expression_ and each component expressione is called a _sub-expression_.
|
||||
|
||||
IMPORTANT: Technically [blue]`;` is not treated as a real operator. It acts as a separator in lists of expressions.
|
||||
@@ -532,38 +637,47 @@ The [blue]`:` symbol (colon) is the separator of the selector-cases. Note that i
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`1 ? {"a"} : {"b"}` +
|
||||
[green]`b` +
|
||||
`>>>` [blue]`10 ? {"a"} : {"b"} :: {"c"}` +
|
||||
[green]`c' +
|
||||
[green]`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}` +
|
||||
[green]`b` +
|
||||
`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}` +
|
||||
[red]`Parse Error: [1:34] case list in default clause` +
|
||||
[green]`>>>` [blue]`10 ? {"a"} :[10] {x="b" but x} :: {"c"}` +
|
||||
[green]`b` +
|
||||
`>>>` [blue]`10 ? {"a"} :[10] {x="b"; x} :: {"c"}` +
|
||||
[green]`b` +
|
||||
[green]`b`
|
||||
|
||||
`>>>` [blue]`10 ? {"a"} : {"b"} {2c} {"c"}` +
|
||||
[green]`c`
|
||||
|
||||
`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} {2c} {"c"}` +
|
||||
[green]`b`
|
||||
|
||||
`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} {2c} [10] {"c"}` +
|
||||
[red]`Parse Error: [1:34] case list in default clause`
|
||||
|
||||
`>>>` [blue]`10 ? {"a"} :[10] {x="b" but x} {2c} {"c"}` +
|
||||
[green]`b`
|
||||
|
||||
`>>>` [blue]`10 ? {"a"} :[10] {x="b"; x} {2c} {"c"}` +
|
||||
[green]`b`
|
||||
|
||||
`>>>` [blue]`10 ? {"a"} : {"b"}` +
|
||||
[red]`Eval Error: [1:3] no case catches the value (10) of the selection expression`
|
||||
|
||||
|
||||
=== Variable default value [blue]`??` and [blue]`?=`
|
||||
The left operand of these two operators must be a variable. The right operator can be any expression. They return the value of the variable if this is define; otherwise they return the value of the right expression.
|
||||
The left operand of these two operators must be a variable. The right operator can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression.
|
||||
|
||||
IMPORTANT: If the left variable is defined, the right expression is not evuated at all.
|
||||
IMPORTANT: If the left variable is defined, the right expression is not evaluated at all.
|
||||
|
||||
The [blue]`??` do not change the status of the left variable.
|
||||
The [blue]`??` operator do not change the status of the left variable.
|
||||
|
||||
The [blue]`?=` assigns the calculated value of the right expression to the left variable.
|
||||
|
||||
.Examples
|
||||
`>>>` [blue]`var ?? (1+2)`'
|
||||
[green]`3` +
|
||||
`>>>` [blue]`var ?? (1+2)`' +
|
||||
[green]`3`
|
||||
|
||||
`>>>` [blue]`var` +
|
||||
[red]`Eval Error: undefined variable or function "var"` +
|
||||
[red]`Eval Error: undefined variable or function "var"`
|
||||
|
||||
`>>>` [blue]`var ?= (1+2)` +
|
||||
[green]`3` +
|
||||
`>>>` [blue]`var` +
|
||||
[green]`3`
|
||||
|
||||
`>>>` [blue]`var`
|
||||
[green]`3`
|
||||
|
||||
NOTE: These operators have a high priority, in particular higher than the operator [blue]`=`.
|
||||
@@ -576,54 +690,87 @@ The table below shows all supported operators by decreasing priorities.
|
||||
|===
|
||||
| Priority | Operators | Position | Operation | Operands and results
|
||||
|
||||
.2+|*ITEM*| [blue]`.` | _Infix_ | _List item_| _list_ `"."` _integer_ -> _any_
|
||||
| [blue]`.` | _Infix_ | _Dict item_ | _dict_ `"."` _any_ -> _any_
|
||||
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `"++"` -> _integer_
|
||||
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `"++"` -> _any_
|
||||
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `"!"` -> _integer_
|
||||
.3+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| (`"+"`\|`"-"`) _number_ -> _number_
|
||||
| [blue]`#` | _Prefix_ | _Lenght-of_ | `"#"` _collection_ -> _integer_
|
||||
| [blue]`#` | _Prefix_ | _Size-of_ | `"#"` _iterator_ -> _integer_
|
||||
.5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ `"*"` _number_ -> _number_
|
||||
| [blue]`*` | _Infix_ | _String-repeat_ | _string_ `"*"` _integer_ -> _string_
|
||||
| [blue]`/` | _Infix_ | _Division_ | _number_ `"/"` _number_ -> _number_
|
||||
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `"./"` _number_ -> _float_
|
||||
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `"%"` _integer_ -> _integer_
|
||||
.6+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `"+"` _number_ -> _number_
|
||||
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `"+"` (_string_\|_number_) -> _string_
|
||||
| [blue]`+` | _Infix_ | _List-join_ | _list_ `"+"` _list_ -> _list_
|
||||
| [blue]`+` | _Infix_ | _Dict-join_ | _dict_ `"+"` _dict_ -> _dict_
|
||||
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `"-"` _number_ -> _number_
|
||||
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `"-"` _list_ -> _list_
|
||||
.8+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ `"<"` _comparable_ -> _boolean_
|
||||
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `"\<="` _comparable_ -> _boolean_
|
||||
| [blue]`>` | _Infix_ | _greater_ | _comparable_ `">"` _comparable_ -> _boolean_
|
||||
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ `">="` _comparable_ -> _boolean_
|
||||
| [blue]`==` | _Infix_ | _equal_ | _comparable_ `"=="` _comparable_ -> _boolean_
|
||||
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ `"!="` _comparable_ -> _boolean_
|
||||
| [blue]`in` | _Infix_ | _member-of-list_ | _any_ `"in"` _list_ -> _boolean_
|
||||
| [blue]`in` | _Infix_ | _key-of-dict_ | _any_ `"in"` _dict_ -> _boolean_
|
||||
.1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | `"not"` _boolean_ -> _boolean_
|
||||
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ `"and"` _boolean_ -> _boolean_
|
||||
| [blue]`&&` | _Infix_ | _and_ | _boolean_ `"&&"` _boolean_ -> _boolean_
|
||||
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ `"or"` _boolean_ -> _boolean_
|
||||
| [blue]`\|\|` | _Infix_ | _or_ | _boolean_ `"\|\|"` _boolean_ -> _boolean_
|
||||
.3+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
|
||||
| [blue]`>>` | _Infix_ | _front-insert_ | _any_ ">>" _list_ -> _list_
|
||||
| [blue]`<<` | _Infix_ | _back-insert_ | _list_ "<<" _any_ -> _list_
|
||||
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
|
||||
.2+|*ITEM*| [blue]`[`...`]` | _Postfix_ | _List item_| _list_ `[` _integer_ `]` -> _any_
|
||||
| [blue]`[`...`]` | _Postfix_ | _Dict item_ | _dict_ `[` _any_ `]` -> _any_
|
||||
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `++` -> _integer_
|
||||
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `++` -> _any_
|
||||
.2+|*DEFAULT*| [blue]`??` | _Infix_ | _Default value_| _variable_ `??` _any-expr_ -> _any_
|
||||
| [blue]`?=` | _Infix_ | _Default/assign value_| _variable_ `?=` _any-expr_ -> _any_
|
||||
.1+| *ITER*^1^| [blue]`()` | _Prefix_ | _Iterator value_ | `()` _iterator_ -> _any_
|
||||
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `!` -> _integer_
|
||||
.3+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| (`+`\|`-`) _number_ -> _number_
|
||||
| [blue]`#` | _Prefix_ | _Lenght-of_ | `#` _collection_ -> _integer_
|
||||
| [blue]`#` | _Prefix_ | _Size-of_ | `#` _iterator_ -> _integer_
|
||||
.2+|*SELECT*| [blue]`? : ::` | _Multi-Infix_ | _Case-Selector_ | _any-expr_ `?` _case-list_ _case-expr_ `:` _case-list_ _case-expr_ ... `::` _default-expr_ -> _any_
|
||||
| [blue]`? : ::` | _Multi-Infix_ | _Index-Selector_ | _int-expr_ `?` _case-expr_ `:` _case-expr_ ... `::` _default-expr_ -> _any_
|
||||
.1+|*FRACT*| [blue]`\|` | _Infix_ | _Fraction_ | _integer_ `\|` _integer_ -> _fraction_
|
||||
.5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ `*` _number_ -> _number_
|
||||
| [blue]`*` | _Infix_ | _String-repeat_ | _string_ `*` _integer_ -> _string_
|
||||
| [blue]`/` | _Infix_ | _Division_ | _number_ `/` _number_ -> _number_
|
||||
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `./` _number_ -> _float_
|
||||
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `%` _integer_ -> _integer_
|
||||
.6+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `+` _number_ -> _number_
|
||||
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `+` (_string_\|_number_) -> _string_
|
||||
| [blue]`+` | _Infix_ | _List-join_ | _list_ `+` _list_ -> _list_
|
||||
| [blue]`+` | _Infix_ | _Dict-join_ | _dict_ `+` _dict_ -> _dict_
|
||||
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `-` _number_ -> _number_
|
||||
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `-` _list_ -> _list_
|
||||
.8+|*RELATION*| [blue]`<` | _Infix_ | _Less_ | _comparable_ `<` _comparable_ -> _boolean_
|
||||
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `\<=` _comparable_ -> _boolean_
|
||||
| [blue]`>` | _Infix_ | _Greater_ | _comparable_ `>` _comparable_ -> _boolean_
|
||||
| [blue]`>=` | _Infix_ | _Greater-equal_ | _comparable_ `>=` _comparable_ -> _boolean_
|
||||
| [blue]`==` | _Infix_ | _Equal_ | _comparable_ `==` _comparable_ -> _boolean_
|
||||
| [blue]`!=` | _Infix_ | _Not-equal_ | _comparable_ `!=` _comparable_ -> _boolean_
|
||||
| [blue]`in` | _Infix_ | _Member-of-list_ | _any_ `in` _list_ -> _boolean_
|
||||
| [blue]`in` | _Infix_ | _Key-of-dict_ | _any_ `in` _dict_ -> _boolean_
|
||||
.1+|*NOT*| [blue]`not` | _Prefix_ | _Not_ | `not` _boolean_ -> _boolean_
|
||||
.2+|*AND*| [blue]`and` | _Infix_ | _And_ | _boolean_ `and` _boolean_ -> _boolean_
|
||||
| [blue]`&&` | _Infix_ | _And_ | _boolean_ `&&` _boolean_ -> _boolean_
|
||||
.2+|*OR*| [blue]`or` | _Infix_ | _Or_ | _boolean_ `or` _boolean_ -> _boolean_
|
||||
| [blue]`\|\|` | _Infix_ | _Or_ | _boolean_ `\|\|` _boolean_ -> _boolean_
|
||||
.3+|*ASSIGN*| [blue]`=` | _Infix_ | _Assignment_ | _identifier_ `=` _any_ -> _any_
|
||||
| [blue]`>>` | _Infix_ | _Front-insert_ | _any_ `>>` _list_ -> _list_
|
||||
| [blue]`<<` | _Infix_ | _Back-insert_ | _list_ `<<` _any_ -> _list_
|
||||
.1+|*BUT*| [blue]`but` | _Infix_ | _But_ | _any_ `but` _any_ -> _any_
|
||||
.1+|*RANGE*| [blue]`:` | _Infix_ | _Index-range_ | _integer_ `:` _integer_ -> _integer-pair_
|
||||
|===
|
||||
|
||||
== Functions
|
||||
Functions in _Expr_ are very similar to functions in many programming languages.
|
||||
^1^ Experimental
|
||||
|
||||
In _Expr_ functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator [blue]`@` it is possibile to export local definition to the calling context.
|
||||
|
||||
== Functions
|
||||
Functions in _Expr_ are very similar to functions available in many programming languages. Actually, _Expr_ supports two types of function, _expr-functions_ and _go-functions_.
|
||||
|
||||
* _expr-functions_ are defined using _Expr_'s syntax. They can be passed as arguments to other functions and can be returned from functions. Moreover, they bind themselves to the defining context, thus becoming closures.
|
||||
* _go-functions_ are regular Golang functions callable from _Expr_ expressions. They are defined in Golang source files called _modules_ and compiled within the _Expr_ package. To make Golang functions available in _Expr_ contextes, it is required to _import_ the module in which they are defined.
|
||||
|
||||
|
||||
=== _Expr_ function definition
|
||||
A function is identified and referenced by its name. It can have zero or more parameter. _Expr_ functions also support optional parameters.
|
||||
|
||||
. Expr's function definition syntax
|
||||
====
|
||||
*_function-definition_* = _identifier_ "**=**" "**func(**" [_param-list_] "**)**" "**{**" _multi-expression_ "**}**"
|
||||
_param_list_ = _required-param-list_ [ "**,**" _optional-param-list_ ]
|
||||
_required-param-list_ = _identifier_ { "**,**" _identifier_ }
|
||||
_optional-param-list_ = _optional-parm_ { "**,**" _optional-param_ }
|
||||
_optional-param_ = _identifier_ "**=**" _any-expr_
|
||||
====
|
||||
|
||||
.Examples
|
||||
#TODO#
|
||||
|
||||
=== _Golang_ function definition
|
||||
Description of how to define Golan functions and how to bind them to _Expr_ are topics treated in another document that I'll write, one day, maybe.
|
||||
|
||||
=== Function calls
|
||||
#TODO: function calls operations#
|
||||
|
||||
=== Function definitions
|
||||
#TODO: function definitions operations#
|
||||
Functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator [blue]`@` it is possibile to export local definition to the calling context.
|
||||
|
||||
|
||||
== Iterators
|
||||
#TODO: function calls operations#
|
||||
|
||||
== Builtins
|
||||
#TODO: builtins#
|
||||
@@ -634,4 +781,7 @@ In _Expr_ functions compute values in a local context (scope) that do not make e
|
||||
[blue]_import([grey]#<source-file>#)_ loads the multi-expression contained in the specified source and returns its value.
|
||||
|
||||
|
||||
== Plugins
|
||||
#TODO: plugins#
|
||||
|
||||
|
||||
|
||||
+540
-214
File diff suppressed because one or more lines are too long
@@ -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)
|
||||
+17
-3
@@ -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,8 +63,10 @@ type Typer interface {
|
||||
TypeName() string
|
||||
}
|
||||
|
||||
func getTypeName(v any) (name string) {
|
||||
if typer, ok := v.(Typer); ok {
|
||||
func TypeName(v any) (name string) {
|
||||
if v == nil {
|
||||
name = "nil"
|
||||
} else if typer, ok := v.(Typer); ok {
|
||||
name = typer.TypeName()
|
||||
} else if IsInteger(v) {
|
||||
name = "integer"
|
||||
|
||||
+4
-46
@@ -33,52 +33,10 @@ func float64ToFraction(f float64) (fract *FractionType, err error) {
|
||||
}
|
||||
dec := fmt.Sprintf("%.12f", decPart)
|
||||
s := fmt.Sprintf("%s%.f%s", sign, intPart, dec[1:])
|
||||
// fmt.Printf("S: '%s'\n",s)
|
||||
return makeGeneratingFraction(s)
|
||||
}
|
||||
|
||||
// Based on https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/math/big/rat.go;l=39
|
||||
/*
|
||||
func _float64ToFraction(f float64) (num, den int64, err error) {
|
||||
const expMask = 1<<11 - 1
|
||||
bits := math.Float64bits(f)
|
||||
mantissa := bits & (1<<52 - 1)
|
||||
exp := int((bits >> 52) & expMask)
|
||||
switch exp {
|
||||
case expMask: // non-finite
|
||||
err = errors.New("infite")
|
||||
return
|
||||
case 0: // denormal
|
||||
exp -= 1022
|
||||
default: // normal
|
||||
mantissa |= 1 << 52
|
||||
exp -= 1023
|
||||
}
|
||||
|
||||
shift := 52 - exp
|
||||
|
||||
// Optimization (?): partially pre-normalise.
|
||||
for mantissa&1 == 0 && shift > 0 {
|
||||
mantissa >>= 1
|
||||
shift--
|
||||
}
|
||||
|
||||
if f < 0 {
|
||||
num = -int64(mantissa)
|
||||
} else {
|
||||
num = int64(mantissa)
|
||||
}
|
||||
den = int64(1)
|
||||
|
||||
if shift > 0 {
|
||||
den = den << shift
|
||||
} else {
|
||||
num = num << (-shift)
|
||||
}
|
||||
return
|
||||
}
|
||||
*/
|
||||
|
||||
func makeGeneratingFraction(s string) (f *FractionType, err error) {
|
||||
var num, den int64
|
||||
var sign int64 = 1
|
||||
@@ -112,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
|
||||
@@ -123,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')
|
||||
@@ -136,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
|
||||
@@ -254,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
@@ -1,150 +0,0 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// func-import.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const ENV_EXPR_PATH = "EXPR_PATH"
|
||||
|
||||
func importFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
return importGeneral(ctx, name, args)
|
||||
}
|
||||
|
||||
func importAllFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
enable(ctx, control_export_all)
|
||||
return importGeneral(ctx, name, args)
|
||||
}
|
||||
|
||||
func importGeneral(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var dirList []string
|
||||
|
||||
dirList = addEnvImportDirs(dirList)
|
||||
dirList = addPresetImportDirs(ctx, dirList)
|
||||
result, err = doImport(ctx, name, dirList, NewArrayIterator(args))
|
||||
return
|
||||
}
|
||||
|
||||
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
|
||||
if !(IsString(paramValue) /*|| isList(paramValue)*/) {
|
||||
err = fmt.Errorf("%s(): param nr %d has wrong type %T, string expected", funcName, paramPos+1, paramValue)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func addEnvImportDirs(dirList []string) []string {
|
||||
if dirSpec, exists := os.LookupEnv(ENV_EXPR_PATH); exists {
|
||||
dirs := strings.Split(dirSpec, ":")
|
||||
if dirList == nil {
|
||||
dirList = dirs
|
||||
} else {
|
||||
dirList = append(dirList, dirs...)
|
||||
}
|
||||
}
|
||||
return dirList
|
||||
}
|
||||
|
||||
func addPresetImportDirs(ctx ExprContext, dirList []string) []string {
|
||||
if dirSpec, exists := getControlString(ctx, ControlImportPath); exists {
|
||||
dirs := strings.Split(dirSpec, ":")
|
||||
if dirList == nil {
|
||||
dirList = dirs
|
||||
} else {
|
||||
dirList = append(dirList, dirs...)
|
||||
}
|
||||
}
|
||||
return dirList
|
||||
}
|
||||
|
||||
func isFile(filePath string) bool {
|
||||
info, err := os.Stat(filePath)
|
||||
return (err == nil || errors.Is(err, os.ErrExist)) && info.Mode().IsRegular()
|
||||
}
|
||||
|
||||
func searchAmongPath(filename string, dirList []string) (filePath string) {
|
||||
for _, dir := range dirList {
|
||||
if fullPath := path.Join(dir, filename); isFile(fullPath) {
|
||||
filePath = fullPath
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func isPathRelative(filePath string) bool {
|
||||
unixPath := filepath.ToSlash(filePath)
|
||||
return strings.HasPrefix(unixPath, "./") || strings.HasPrefix(unixPath, "../")
|
||||
}
|
||||
|
||||
func makeFilepath(filename string, dirList []string) (filePath string, err error) {
|
||||
if path.IsAbs(filename) || isPathRelative(filename) {
|
||||
if isFile(filename) {
|
||||
filePath = filename
|
||||
}
|
||||
} else {
|
||||
filePath = searchAmongPath(filename, dirList)
|
||||
}
|
||||
if len(filePath) == 0 {
|
||||
err = fmt.Errorf("source file %q not found", filename)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (result any, err error) {
|
||||
var v any
|
||||
var sourceFilepath string
|
||||
|
||||
for v, err = it.Next(); err == nil; v, err = it.Next() {
|
||||
if err = checkStringParamExpected(name, v, it.Index()); err != nil {
|
||||
break
|
||||
}
|
||||
if sourceFilepath, err = makeFilepath(v.(string), dirList); err != nil {
|
||||
break
|
||||
}
|
||||
var file *os.File
|
||||
if file, err = os.Open(sourceFilepath); err == nil {
|
||||
defer file.Close()
|
||||
var expr *ast
|
||||
scanner := NewScanner(file, DefaultTranslations())
|
||||
parser := NewParser(ctx)
|
||||
if expr, err = parser.parseGeneral(scanner, true, true); err == nil {
|
||||
result, err = expr.eval(ctx, false)
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
} else {
|
||||
result = nil
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ImportImportFuncs(ctx ExprContext) {
|
||||
ctx.RegisterFunc("import", newGolangFunctor(importFunc), typeAny, []ExprFuncParam{
|
||||
newFuncParamFlag(typeFilepath, pfRepeat),
|
||||
})
|
||||
ctx.RegisterFunc("importAll", newGolangFunctor(importAllFunc), typeAny, []ExprFuncParam{
|
||||
newFuncParamFlag(typeFilepath, pfRepeat),
|
||||
})
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerImport("import", ImportImportFuncs, "Functions import() and include()")
|
||||
}
|
||||
+87
-78
@@ -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,17 @@ 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
|
||||
func (functor *baseFunctor) GetDefinitionContext() ExprContext {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ---- Function Parameters
|
||||
type paramFlags uint16
|
||||
|
||||
const (
|
||||
pfOptional paramFlags = 1 << iota
|
||||
pfRepeat
|
||||
PfDefault paramFlags = 1 << iota
|
||||
PfOptional
|
||||
PfRepeat
|
||||
)
|
||||
|
||||
type funcParamInfo struct {
|
||||
@@ -109,15 +61,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 +78,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 {
|
||||
@@ -142,13 +98,15 @@ func (param *funcParamInfo) DefaultValue() any {
|
||||
}
|
||||
|
||||
// --- Functions
|
||||
|
||||
// funcInfo implements ExprFunc
|
||||
type funcInfo struct {
|
||||
name string
|
||||
minArgs int
|
||||
maxArgs int
|
||||
functor Functor
|
||||
params []ExprFuncParam
|
||||
returnType string
|
||||
name string
|
||||
minArgs int
|
||||
maxArgs int
|
||||
functor Functor
|
||||
formalParams []ExprFuncParam
|
||||
returnType string
|
||||
}
|
||||
|
||||
func newFuncInfo(name string, functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
|
||||
@@ -159,7 +117,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++
|
||||
@@ -168,23 +126,24 @@ func newFuncInfo(name string, functor Functor, returnType string, params []ExprF
|
||||
return nil, fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name())
|
||||
}
|
||||
if p.IsRepeat() {
|
||||
minArgs--
|
||||
maxArgs = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
info = &funcInfo{
|
||||
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, params: params,
|
||||
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, formalParams: params,
|
||||
}
|
||||
functor.SetFunc(info)
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func newUnnamedFuncInfo(functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
|
||||
return newFuncInfo("unnamed", functor, returnType, params)
|
||||
}
|
||||
// func newUnnamedFuncInfo(functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
|
||||
// return newFuncInfo("unnamed", functor, returnType, params)
|
||||
// }
|
||||
|
||||
func (info *funcInfo) Params() []ExprFuncParam {
|
||||
return info.params
|
||||
return info.formalParams
|
||||
}
|
||||
|
||||
func (info *funcInfo) ReturnType() string {
|
||||
@@ -199,14 +158,14 @@ func (info *funcInfo) ToString(opt FmtOpt) string {
|
||||
sb.WriteString(info.Name())
|
||||
}
|
||||
sb.WriteByte('(')
|
||||
if info.params != nil {
|
||||
for i, p := range info.params {
|
||||
if info.formalParams != nil {
|
||||
for i, p := range info.formalParams {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(p.Name())
|
||||
|
||||
if p.IsOptional() {
|
||||
if p.IsDefault() {
|
||||
sb.WriteByte('=')
|
||||
if s, ok := p.DefaultValue().(string); ok {
|
||||
sb.WriteByte('"')
|
||||
@@ -221,13 +180,13 @@ func (info *funcInfo) ToString(opt FmtOpt) string {
|
||||
if info.maxArgs < 0 {
|
||||
sb.WriteString(" ...")
|
||||
}
|
||||
sb.WriteString("): ")
|
||||
sb.WriteString("):")
|
||||
if len(info.returnType) > 0 {
|
||||
sb.WriteString(info.returnType)
|
||||
} else {
|
||||
sb.WriteString(typeAny)
|
||||
sb.WriteString(TypeAny)
|
||||
}
|
||||
sb.WriteString(" {<body>}")
|
||||
sb.WriteString("{}")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
@@ -246,3 +205,53 @@ func (info *funcInfo) MaxArgs() int {
|
||||
func (info *funcInfo) Functor() Functor {
|
||||
return info.functor
|
||||
}
|
||||
|
||||
func (info *funcInfo) AllocContext(parentCtx ExprContext) (ctx ExprContext) {
|
||||
if defCtx := info.functor.GetDefinitionContext(); defCtx != nil {
|
||||
ctx = defCtx.Clone()
|
||||
ctx.SetParent(defCtx)
|
||||
} else {
|
||||
ctx = parentCtx.Clone()
|
||||
ctx.SetParent(parentCtx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (info *funcInfo) PrepareCall(parentCtx ExprContext, name string, varActualParams *[]any) (ctx ExprContext, err error) {
|
||||
passedCount := len(*varActualParams)
|
||||
if info.MinArgs() > passedCount {
|
||||
err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
|
||||
}
|
||||
|
||||
for i := passedCount; i < len(info.formalParams); i++ {
|
||||
p := info.formalParams[i]
|
||||
if !p.IsDefault() {
|
||||
break
|
||||
}
|
||||
*varActualParams = append(*varActualParams, p.DefaultValue())
|
||||
}
|
||||
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(*varActualParams) {
|
||||
err = ErrTooMuchParams(name, info.MaxArgs(), len(*varActualParams))
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
ctx = info.AllocContext(parentCtx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ----- Call a function ---
|
||||
|
||||
func CallFunction(parentCtx ExprContext, name string, actualParams []any) (result any, err error) {
|
||||
if info, exists, _ := GetFuncInfo(parentCtx, name); exists {
|
||||
var ctx ExprContext
|
||||
if ctx, err = info.PrepareCall(parentCtx, name, &actualParams); err == nil {
|
||||
functor := info.Functor()
|
||||
result, err = functor.Invoke(ctx, name, actualParams)
|
||||
exportObjectsToParent(ctx)
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("unknown function %s()", name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
+96
-8
@@ -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++
|
||||
@@ -40,16 +43,101 @@ func GetVar(ctx ExprContext, name string) (value any, exists bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func GetLocalFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool) {
|
||||
var v any
|
||||
if len(name) > 0 {
|
||||
if v, exists = ctx.GetVar(name); exists && isFunctor(v) {
|
||||
f, _ := v.(Functor)
|
||||
item = f.GetFunc()
|
||||
} else {
|
||||
item, exists = ctx.GetFuncInfo(name)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool, ownerCtx ExprContext) {
|
||||
if item, exists = ctx.GetFuncInfo(name); exists {
|
||||
ownerCtx = ctx
|
||||
} else if item, exists = globalCtx.GetFuncInfo(name); exists {
|
||||
ownerCtx = globalCtx
|
||||
if len(name) > 0 {
|
||||
if item, exists = GetLocalFuncInfo(ctx, name); exists {
|
||||
ownerCtx = ctx
|
||||
} else if item, exists = globalCtx.GetFuncInfo(name); exists {
|
||||
ownerCtx = globalCtx
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GlobalCtrlSet(name string, newValue any) (currentValue any) {
|
||||
if !strings.HasPrefix(name, "_") {
|
||||
name = "_" + name
|
||||
}
|
||||
currentValue, _ = globalCtx.GetVar(name)
|
||||
|
||||
globalCtx.SetVar(name, newValue)
|
||||
return currentValue
|
||||
}
|
||||
|
||||
func GlobalCtrlGet(name string) (currentValue any) {
|
||||
if !strings.HasPrefix(name, "_") {
|
||||
name = "_" + name
|
||||
}
|
||||
currentValue, _ = globalCtx.GetVar(name)
|
||||
return currentValue
|
||||
}
|
||||
|
||||
func CtrlEnable(ctx ExprContext, name string) (currentStatus bool) {
|
||||
if !strings.HasPrefix(name, "_") {
|
||||
name = "_" + name
|
||||
}
|
||||
if v, exists := ctx.GetVar(name); exists && IsBool(v) {
|
||||
currentStatus, _ = v.(bool)
|
||||
}
|
||||
|
||||
ctx.SetVar(name, true)
|
||||
return currentStatus
|
||||
}
|
||||
|
||||
func CtrlDisable(ctx ExprContext, name string) (currentStatus bool) {
|
||||
if !strings.HasPrefix(name, "_") {
|
||||
name = "_" + name
|
||||
}
|
||||
if v, exists := ctx.GetVar(name); exists && IsBool(v) {
|
||||
currentStatus, _ = v.(bool)
|
||||
}
|
||||
|
||||
ctx.SetVar(name, false)
|
||||
return currentStatus
|
||||
}
|
||||
|
||||
func CtrlIsEnabled(ctx ExprContext, name string) (status bool) {
|
||||
var v any
|
||||
var exists bool
|
||||
|
||||
if !strings.HasPrefix(name, "_") {
|
||||
name = "_" + name
|
||||
}
|
||||
|
||||
if v, exists = ctx.GetVar(name); !exists {
|
||||
v, exists = globalCtx.GetVar(name)
|
||||
}
|
||||
|
||||
if exists {
|
||||
if b, ok := v.(bool); ok {
|
||||
status = b
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getControlString(name string) (s string, exists bool) {
|
||||
var v any
|
||||
if v, exists = globalCtx.GetVar(name); exists {
|
||||
s, exists = v.(string)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func init() {
|
||||
globalCtx = NewSimpleStore()
|
||||
initDefaultVars(globalCtx)
|
||||
ImportBuiltinsFuncs(globalCtx)
|
||||
}
|
||||
|
||||
+6
-6
@@ -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
@@ -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
|
||||
}
|
||||
+15
-6
@@ -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 := ToGoInt(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 := ToGoInt(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 := ToGoInt(args[2], "step"); err == nil {
|
||||
if i < 0 {
|
||||
i = -i
|
||||
}
|
||||
@@ -106,10 +106,19 @@ func (it *ListIterator) CallOperation(name string, args []any) (v any, err error
|
||||
|
||||
func (it *ListIterator) Current() (item any, err error) {
|
||||
a := *(it.a)
|
||||
if it.index >= 0 && it.index <= it.stop {
|
||||
item = a[it.index]
|
||||
if it.start <= it.stop {
|
||||
if it.stop < len(a) && it.index >= it.start && it.index <= it.stop {
|
||||
item = a[it.index]
|
||||
} else {
|
||||
err = io.EOF
|
||||
}
|
||||
} else {
|
||||
err = io.EOF
|
||||
if it.start < len(a) && it.index >= it.stop && it.index <= it.start {
|
||||
item = a[it.index]
|
||||
} else {
|
||||
err = io.EOF
|
||||
}
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ type ExtIterator interface {
|
||||
}
|
||||
|
||||
func errNoOperation(name string) error {
|
||||
return fmt.Errorf("no %q function defined in the data-source", name)
|
||||
return fmt.Errorf("no %s() function defined in the data-source", name)
|
||||
}
|
||||
|
||||
func errInvalidDataSource() error {
|
||||
|
||||
+51
-6
@@ -13,10 +13,17 @@ import (
|
||||
type ListType []any
|
||||
|
||||
func newListA(listAny ...any) (list *ListType) {
|
||||
if listAny == nil {
|
||||
listAny = []any{}
|
||||
}
|
||||
return newList(listAny)
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -27,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(", ")
|
||||
}
|
||||
@@ -46,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
|
||||
@@ -147,3 +182,13 @@ func deepSame(a, b any, deepCmp deepFuncTemplate) (eq bool, err error) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (list *ListType) setItem(index int64, value any) (err error) {
|
||||
if index >= 0 && index < int64(len(*list)) {
|
||||
(*list)[index] = value
|
||||
} else {
|
||||
err = fmt.Errorf("index %d out of bounds (0, %d)", index, len(*list)-1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// module-register.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type module struct {
|
||||
importFunc func(ExprContext)
|
||||
description string
|
||||
imported bool
|
||||
}
|
||||
|
||||
func newModule(importFunc func(ExprContext), description string) *module {
|
||||
return &module{importFunc, description, false}
|
||||
}
|
||||
|
||||
var moduleRegister map[string]*module
|
||||
|
||||
func registerImport(name string, importFunc func(ExprContext), description string) {
|
||||
if moduleRegister == nil {
|
||||
moduleRegister = make(map[string]*module)
|
||||
}
|
||||
if _, exists := moduleRegister[name]; exists {
|
||||
panic(fmt.Errorf("module %q already registered", name))
|
||||
}
|
||||
moduleRegister[name] = newModule(importFunc, description)
|
||||
}
|
||||
|
||||
func IterateModules(op func(name, description string, imported bool) bool) {
|
||||
if op != nil {
|
||||
for name, mod := range moduleRegister {
|
||||
if !op(name, mod.description, mod.imported) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----
|
||||
func init() {
|
||||
if moduleRegister == nil {
|
||||
moduleRegister = make(map[string]*module)
|
||||
}
|
||||
}
|
||||
-118
@@ -4,124 +4,6 @@
|
||||
// operand-dict.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DictType map[any]any
|
||||
|
||||
func newDict(dictAny map[any]*term) (dict *DictType) {
|
||||
var d DictType
|
||||
if dictAny != nil {
|
||||
d = make(DictType, len(dictAny))
|
||||
for i, item := range dictAny {
|
||||
d[i] = item
|
||||
}
|
||||
} else {
|
||||
d = make(DictType)
|
||||
}
|
||||
dict = &d
|
||||
return
|
||||
}
|
||||
|
||||
func (dict *DictType) toMultiLine(sb *strings.Builder, indent int) {
|
||||
sb.WriteString(strings.Repeat("\t", indent))
|
||||
sb.WriteString("{\n")
|
||||
|
||||
first := true
|
||||
for name, value := range *dict {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
sb.WriteByte(',')
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
|
||||
sb.WriteString(strings.Repeat("\t", indent+1))
|
||||
if key, ok := name.(string); ok {
|
||||
sb.WriteString(string('"') + key + string('"'))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", name))
|
||||
}
|
||||
sb.WriteString(": ")
|
||||
if f, ok := value.(Formatter); ok {
|
||||
sb.WriteString(f.ToString(MultiLine))
|
||||
} else if _, ok = value.(Functor); ok {
|
||||
sb.WriteString("func(){}")
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", value))
|
||||
}
|
||||
}
|
||||
sb.WriteString(strings.Repeat("\t", indent))
|
||||
sb.WriteString("\n}")
|
||||
}
|
||||
|
||||
func (dict *DictType) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
if opt&MultiLine != 0 {
|
||||
dict.toMultiLine(&sb, 0)
|
||||
} else {
|
||||
sb.WriteByte('{')
|
||||
first := true
|
||||
for key, value := range *dict {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
if s, ok := key.(string); ok {
|
||||
sb.WriteString(string('"') + s + string('"'))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", key))
|
||||
}
|
||||
sb.WriteString(": ")
|
||||
if formatter, ok := value.(Formatter); ok {
|
||||
sb.WriteString(formatter.ToString(opt))
|
||||
} else if t, ok := value.(*term); ok {
|
||||
sb.WriteString(t.String())
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%#v", value))
|
||||
}
|
||||
}
|
||||
sb.WriteByte('}')
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (dict *DictType) String() string {
|
||||
return dict.ToString(0)
|
||||
}
|
||||
|
||||
func (dict *DictType) TypeName() string {
|
||||
return "dict"
|
||||
}
|
||||
|
||||
func (dict *DictType) hasKey(target any) (ok bool) {
|
||||
for key := range *dict {
|
||||
if ok = reflect.DeepEqual(key, target); ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (dict *DictType) clone() (c *DictType) {
|
||||
c = newDict(nil)
|
||||
for k, v := range *dict {
|
||||
(*c)[k] = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (dict *DictType) merge(second *DictType) {
|
||||
if second != nil {
|
||||
for k, v := range *second {
|
||||
(*dict)[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------- dict term
|
||||
func newDictTerm(args map[any]*term) *term {
|
||||
|
||||
+4
-52
@@ -6,7 +6,6 @@ package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// -------- function call term
|
||||
@@ -22,37 +21,8 @@ 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.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
|
||||
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)
|
||||
// fmt.Printf("Call %s(), context: %p\n", name, ctx)
|
||||
params := make([]any, len(self.children), len(self.children)+5)
|
||||
for i, tree := range self.children {
|
||||
var param any
|
||||
@@ -63,11 +33,7 @@ func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
if err = checkFunctionCall(ctx, name, ¶ms); err == nil {
|
||||
if v, err = ctx.Call(name, params); err == nil {
|
||||
exportObjects(parentCtx, ctx)
|
||||
}
|
||||
}
|
||||
v, err = CallFunction(ctx, name, params)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -85,20 +51,6 @@ func newFuncDefTerm(tk *Token, args []*term) *term {
|
||||
}
|
||||
|
||||
// -------- eval func def
|
||||
// func _evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
|
||||
// bodySpec := self.value()
|
||||
// if expr, ok := bodySpec.(*ast); ok {
|
||||
// paramList := make([]string, 0, len(self.children))
|
||||
// for _, param := range self.children {
|
||||
// paramList = append(paramList, param.source())
|
||||
// }
|
||||
// v = newExprFunctor(expr, paramList, ctx)
|
||||
// } else {
|
||||
// err = errors.New("invalid function definition: the body specification must be an expression")
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
|
||||
bodySpec := self.value()
|
||||
if expr, ok := bodySpec.(*ast); ok {
|
||||
@@ -107,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)
|
||||
|
||||
@@ -148,23 +148,6 @@ func evalIterator(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// func evalChildren(ctx ExprContext, terms []*term, firstChildValue any) (list *ListType, err error) {
|
||||
// items := make(ListType, len(terms))
|
||||
// for i, tree := range terms {
|
||||
// var param any
|
||||
// if i == 0 && firstChildValue != nil {
|
||||
// param = firstChildValue
|
||||
// } else if param, err = tree.compute(ctx); err != nil {
|
||||
// break
|
||||
// }
|
||||
// items[i] = param
|
||||
// }
|
||||
// if err == nil {
|
||||
// list = &items
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
func evalSibling(ctx ExprContext, terms []*term, firstChildValue any) (list []any, err error) {
|
||||
items := make([]any, 0, len(terms))
|
||||
for i, tree := range terms {
|
||||
|
||||
+54
-9
@@ -16,14 +16,59 @@ func newAssignTerm(tk *Token) (inst *term) {
|
||||
}
|
||||
}
|
||||
|
||||
func assignCollectionItem(ctx ExprContext, collectionTerm, keyListTerm *term, value any) (err error) {
|
||||
var collectionValue, keyListValue, keyValue any
|
||||
var keyList *ListType
|
||||
var ok bool
|
||||
|
||||
if collectionValue, err = collectionTerm.compute(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if keyListValue, err = keyListTerm.compute(ctx); err != nil {
|
||||
return
|
||||
} else if keyList, ok = keyListValue.(*ListType); !ok || len(*keyList) != 1 {
|
||||
err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, TypeName(keyListValue))
|
||||
return
|
||||
}
|
||||
if keyValue = (*keyList)[0]; keyValue == nil {
|
||||
err = keyListTerm.Errorf("index/key is nil")
|
||||
return
|
||||
}
|
||||
|
||||
switch collection := collectionValue.(type) {
|
||||
case *ListType:
|
||||
if index, ok := keyValue.(int64); ok {
|
||||
err = collection.setItem(index, value)
|
||||
} else {
|
||||
err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, TypeName(keyValue))
|
||||
}
|
||||
case *DictType:
|
||||
err = collection.setItem(keyValue, value)
|
||||
default:
|
||||
err = collectionTerm.Errorf("collection expected")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func assignValue(ctx ExprContext, leftTerm *term, v any) (err error) {
|
||||
if leftTerm.symbol() == SymIndex {
|
||||
err = assignCollectionItem(ctx, leftTerm.children[0], leftTerm.children[1], v)
|
||||
} else {
|
||||
ctx.UnsafeSetVar(leftTerm.source(), v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func evalAssign(ctx ExprContext, self *term) (v any, err error) {
|
||||
if err = self.checkOperands(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
leftTerm := self.children[0]
|
||||
if leftTerm.tk.Sym != SymVariable {
|
||||
err = leftTerm.tk.Errorf("left operand of %q must be a variable", self.tk.source)
|
||||
leftSym := leftTerm.symbol()
|
||||
if leftSym != SymVariable && leftSym != SymIndex {
|
||||
err = leftTerm.tk.Errorf("left operand of %q must be a variable or a collection's item", self.tk.source)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -31,22 +76,22 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
|
||||
|
||||
if v, err = rightChild.compute(ctx); err == nil {
|
||||
if functor, ok := v.(Functor); ok {
|
||||
funcName := rightChild.source()
|
||||
if info, exists, _ := GetFuncInfo(ctx, funcName); exists {
|
||||
// ctx.RegisterFuncInfo(info)
|
||||
if info := functor.GetFunc(); info != nil {
|
||||
ctx.RegisterFunc(leftTerm.source(), info.Functor(), info.ReturnType(), info.Params())
|
||||
} else if funcDef, ok := functor.(*exprFunctor); ok {
|
||||
// paramSpecs := ForAll(funcDef.params, newFuncParam)
|
||||
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()", funcName)
|
||||
err = self.Errorf("unknown function %s()", rightChild.source())
|
||||
}
|
||||
} else {
|
||||
ctx.UnsafeSetVar(leftTerm.source(), v)
|
||||
err = assignValue(ctx, leftTerm, v)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
v = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+15
-19
@@ -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)
|
||||
@@ -37,9 +37,7 @@ func evalNot(ctx ExprContext, self *term) (v any, err error) {
|
||||
|
||||
func newAndTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
// class: classOperator,
|
||||
// kind: kindBool,
|
||||
tk: *tk,
|
||||
children: make([]*term, 0, 2),
|
||||
position: posInfix,
|
||||
priority: priAnd,
|
||||
@@ -48,7 +46,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 +63,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 +85,13 @@ func evalAndWithShortcut(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if leftBool, lok := toBool(leftValue); !lok {
|
||||
err = fmt.Errorf("got %T as left operand type of 'and' operator, it must be bool", leftBool)
|
||||
if leftBool, lok := ToBool(leftValue); !lok {
|
||||
err = fmt.Errorf("got %s as left operand type of 'AND' operator, it must be bool", TypeName(leftValue))
|
||||
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)
|
||||
@@ -106,9 +104,7 @@ func evalAndWithShortcut(ctx ExprContext, self *term) (v any, err error) {
|
||||
|
||||
func newOrTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
// class: classOperator,
|
||||
// kind: kindBool,
|
||||
tk: *tk,
|
||||
children: make([]*term, 0, 2),
|
||||
position: posInfix,
|
||||
priority: priOr,
|
||||
@@ -117,7 +113,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 +130,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 +152,13 @@ func evalOrWithShortcut(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if leftBool, lok := toBool(leftValue); !lok {
|
||||
err = fmt.Errorf("got %T as left operand type of 'or' operator, it must be bool", leftBool)
|
||||
if leftBool, lok := ToBool(leftValue); !lok {
|
||||
err = fmt.Errorf("got %s as left operand type of 'OR' operator, it must be bool", TypeName(leftValue))
|
||||
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
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 = ToGoInt((*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
@@ -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, _ = ToGoInt(count, "")
|
||||
} else {
|
||||
v = int64(it.Index() + 1)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -6,19 +6,15 @@ package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
//-------- parser
|
||||
|
||||
type parser struct {
|
||||
ctx ExprContext
|
||||
}
|
||||
|
||||
func NewParser(ctx ExprContext) (p *parser) {
|
||||
p = &parser{
|
||||
ctx: ctx,
|
||||
}
|
||||
func NewParser() (p *parser) {
|
||||
p = &parser{}
|
||||
return p
|
||||
}
|
||||
|
||||
@@ -329,14 +325,22 @@ 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
|
||||
var tk *Token
|
||||
tree = NewAst()
|
||||
firstToken := true
|
||||
lastSym := SymUnknown
|
||||
for tk = scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); tk = scanner.Next() {
|
||||
// lastSym := SymUnknown
|
||||
for tk = scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); /*&& !areSymbolsOutOfCtx(tk, selectorTerm, SymColon, SymDoubleColon)*/ tk = scanner.Next() {
|
||||
if tk.Sym == SymComment {
|
||||
continue
|
||||
}
|
||||
@@ -404,9 +408,9 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
||||
}
|
||||
}
|
||||
case SymEqual:
|
||||
if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
|
||||
currentTerm, err = tree.addToken2(tk)
|
||||
}
|
||||
// if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
|
||||
currentTerm, err = tree.addToken2(tk)
|
||||
// }
|
||||
case SymFuncDef:
|
||||
var funcDefTerm *term
|
||||
if funcDefTerm, err = self.parseFuncDef(scanner); err == nil {
|
||||
@@ -442,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)
|
||||
}
|
||||
@@ -450,7 +458,7 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
||||
selectorTerm = nil
|
||||
|
||||
}
|
||||
lastSym = tk.Sym
|
||||
// lastSym = tk.Sym
|
||||
}
|
||||
if err == nil {
|
||||
err = tk.Error()
|
||||
@@ -458,9 +466,9 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
||||
return
|
||||
}
|
||||
|
||||
func checkPrevSymbol(lastSym, wantedSym Symbol, tk *Token) (err error) {
|
||||
if lastSym != wantedSym {
|
||||
err = fmt.Errorf(`assign operator (%q) must be preceded by a variable`, tk.source)
|
||||
}
|
||||
return
|
||||
}
|
||||
// func checkPrevSymbol(lastSym, wantedSym Symbol, tk *Token) (err error) {
|
||||
// if lastSym != wantedSym {
|
||||
// err = fmt.Errorf(`assign operator (%q) must be preceded by a variable`, tk.source)
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
+107
@@ -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
@@ -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
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// simple-func-store.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SimpleFuncStore struct {
|
||||
SimpleVarStore
|
||||
funcStore map[string]*funcInfo
|
||||
}
|
||||
|
||||
type paramFlags uint16
|
||||
|
||||
const (
|
||||
pfOptional paramFlags = 1 << iota
|
||||
pfRepeat
|
||||
)
|
||||
|
||||
type funcParamInfo struct {
|
||||
name string
|
||||
flags paramFlags
|
||||
defaultValue any
|
||||
}
|
||||
|
||||
func newFuncParam(name string) *funcParamInfo {
|
||||
return &funcParamInfo{name: name}
|
||||
}
|
||||
|
||||
func newFuncParamFlag(name string, flags paramFlags) *funcParamInfo {
|
||||
return &funcParamInfo{name: name, flags: flags}
|
||||
}
|
||||
|
||||
func newFuncParamFlagDef(name string, flags paramFlags, defValue any) *funcParamInfo {
|
||||
return &funcParamInfo{name: name, flags: flags, defaultValue: defValue}
|
||||
}
|
||||
|
||||
func (param *funcParamInfo) Name() string {
|
||||
return param.name
|
||||
}
|
||||
|
||||
func (param *funcParamInfo) Type() string {
|
||||
return "any"
|
||||
}
|
||||
|
||||
func (param *funcParamInfo) IsOptional() bool {
|
||||
return (param.flags & pfOptional) != 0
|
||||
}
|
||||
|
||||
func (param *funcParamInfo) IsRepeat() bool {
|
||||
return (param.flags & pfRepeat) != 0
|
||||
}
|
||||
|
||||
func (param *funcParamInfo) DefaultValue() any {
|
||||
return param.defaultValue
|
||||
}
|
||||
|
||||
type funcInfo struct {
|
||||
name string
|
||||
minArgs int
|
||||
maxArgs int
|
||||
functor Functor
|
||||
params []ExprFuncParam
|
||||
returnType string
|
||||
}
|
||||
|
||||
func (info *funcInfo) Params() []ExprFuncParam {
|
||||
return info.params
|
||||
}
|
||||
|
||||
func (info *funcInfo) ReturnType() string {
|
||||
return info.returnType
|
||||
}
|
||||
|
||||
func (info *funcInfo) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteByte('(')
|
||||
if info.params != nil {
|
||||
for i, p := range info.params {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(p.Name())
|
||||
if p.IsOptional() {
|
||||
sb.WriteByte('=')
|
||||
if s, ok := p.DefaultValue().(string); ok {
|
||||
sb.WriteByte('"')
|
||||
sb.WriteString(s)
|
||||
sb.WriteByte('"')
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", p.DefaultValue()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if info.maxArgs < 0 {
|
||||
sb.WriteString(" ...")
|
||||
}
|
||||
sb.WriteString(") -> ")
|
||||
if len(info.returnType) > 0 {
|
||||
sb.WriteString(info.returnType)
|
||||
} else {
|
||||
sb.WriteString(typeAny)
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (info *funcInfo) Name() string {
|
||||
return info.name
|
||||
}
|
||||
|
||||
func (info *funcInfo) MinArgs() int {
|
||||
return info.minArgs
|
||||
}
|
||||
|
||||
func (info *funcInfo) MaxArgs() int {
|
||||
return info.maxArgs
|
||||
}
|
||||
|
||||
func (info *funcInfo) Functor() Functor {
|
||||
return info.functor
|
||||
}
|
||||
|
||||
func NewSimpleFuncStore() *SimpleFuncStore {
|
||||
ctx := &SimpleFuncStore{
|
||||
SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
|
||||
funcStore: make(map[string]*funcInfo),
|
||||
}
|
||||
//ImportBuiltinsFuncs(ctx)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) Clone() ExprContext {
|
||||
svs := ctx.SimpleVarStore
|
||||
return &SimpleFuncStore{
|
||||
// SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
|
||||
SimpleVarStore: SimpleVarStore{varStore: svs.cloneVars()},
|
||||
funcStore: CloneFilteredMap(ctx.funcStore, func(name string) bool { return name[0] != '@' }),
|
||||
}
|
||||
}
|
||||
|
||||
func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
||||
sb.WriteString("funcs: {\n")
|
||||
first := true
|
||||
names := ctx.EnumFuncs(func(name string) bool { return true })
|
||||
slices.Sort(names)
|
||||
for _, name := range names {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
sb.WriteByte(',')
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
value, _ := ctx.GetFuncInfo(name)
|
||||
sb.WriteString(strings.Repeat("\t", indent+1))
|
||||
sb.WriteString(name)
|
||||
//sb.WriteString("=")
|
||||
if formatter, ok := value.(Formatter); ok {
|
||||
sb.WriteString(formatter.ToString(0))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", value))
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n}\n")
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(ctx.SimpleVarStore.ToString(opt))
|
||||
funcsCtxToBuilder(&sb, ctx, 0)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
|
||||
info, exists = ctx.funcStore[name]
|
||||
return
|
||||
}
|
||||
|
||||
// func (ctx *SimpleFuncStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
|
||||
// ctx.funcStore[name] = &funcInfo{name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor}
|
||||
// }
|
||||
|
||||
func (ctx *SimpleFuncStore) RegisterFuncInfo(info ExprFunc) {
|
||||
ctx.funcStore[info.Name()], _ = info.(*funcInfo)
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) RegisterFunc2(name string, functor Functor, returnType string, params []ExprFuncParam) error {
|
||||
var minArgs = 0
|
||||
var maxArgs = 0
|
||||
if params != nil {
|
||||
for _, p := range params {
|
||||
if maxArgs == -1 {
|
||||
return fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name())
|
||||
// } else if p.IsRepeat() {
|
||||
// maxArgs = -1
|
||||
// continue
|
||||
}
|
||||
if p.IsOptional() {
|
||||
maxArgs++
|
||||
} else if maxArgs == minArgs {
|
||||
minArgs++
|
||||
maxArgs++
|
||||
} else {
|
||||
return fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name())
|
||||
}
|
||||
if p.IsRepeat() {
|
||||
maxArgs = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.funcStore[name] = &funcInfo{
|
||||
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, params: params,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
|
||||
funcNames = make([]string, 0)
|
||||
for name := range ctx.funcStore {
|
||||
if acceptor != nil {
|
||||
if acceptor(name) {
|
||||
funcNames = append(funcNames, name)
|
||||
}
|
||||
} else {
|
||||
funcNames = append(funcNames, name)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) Call(name string, args []any) (result any, err error) {
|
||||
if info, exists := ctx.funcStore[name]; exists {
|
||||
functor := info.functor
|
||||
result, err = functor.Invoke(ctx, name, args)
|
||||
} else {
|
||||
err = fmt.Errorf("unknown function %s()", name)
|
||||
}
|
||||
return
|
||||
}
|
||||
+47
-57
@@ -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,87 +27,71 @@ 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{
|
||||
clone := &SimpleStore{
|
||||
varStore: CloneFilteredMap(ctx.varStore, filterRefName),
|
||||
funcStore: CloneFilteredMap(ctx.funcStore, filterRefName),
|
||||
}
|
||||
return clone
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) Merge(src ExprContext) {
|
||||
for _, name := range src.EnumVars(filterRefName) {
|
||||
ctx.varStore[name], _ = src.GetVar(name)
|
||||
}
|
||||
for _, name := range src.EnumFuncs(filterRefName) {
|
||||
ctx.funcStore[name], _ = src.GetFuncInfo(name)
|
||||
}
|
||||
// func (ctx *SimpleStore) Merge(src ExprContext) {
|
||||
// for _, name := range src.EnumVars(filterRefName) {
|
||||
// ctx.varStore[name], _ = src.GetVar(name)
|
||||
// }
|
||||
// for _, name := range src.EnumFuncs(filterRefName) {
|
||||
// ctx.funcStore[name], _ = src.GetFuncInfo(name)
|
||||
// }
|
||||
// }
|
||||
|
||||
func (ctx *SimpleStore) ToString(opt FmtOpt) string {
|
||||
dict := ctx.ToDict()
|
||||
return dict.ToString(opt)
|
||||
}
|
||||
|
||||
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
||||
sb.WriteString("vars: {\n")
|
||||
first := true
|
||||
for _, name := range ctx.EnumVars(filterPrivName) {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
sb.WriteByte(',')
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) varsToDict(dict *DictType) *DictType {
|
||||
names := ctx.EnumVars(nil)
|
||||
slices.Sort(names)
|
||||
for _, name := range ctx.EnumVars(nil) {
|
||||
value, _ := ctx.GetVar(name)
|
||||
sb.WriteString(strings.Repeat("\t", indent+1))
|
||||
sb.WriteString(name)
|
||||
sb.WriteString(": ")
|
||||
if f, ok := value.(Formatter); ok {
|
||||
sb.WriteString(f.ToString(0))
|
||||
(*dict)[name] = f.ToString(0)
|
||||
} else if _, ok = value.(Functor); ok {
|
||||
sb.WriteString("func(){}")
|
||||
// } else if _, ok = value.(map[any]any); ok {
|
||||
// sb.WriteString("dict{}")
|
||||
(*dict)[name] = "func(){}"
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", value))
|
||||
(*dict)[name] = fmt.Sprintf("%v", value)
|
||||
}
|
||||
}
|
||||
sb.WriteString(strings.Repeat("\t", indent))
|
||||
sb.WriteString("\n}\n")
|
||||
return dict
|
||||
}
|
||||
|
||||
func varsCtxToString(ctx ExprContext, indent int) string {
|
||||
var sb strings.Builder
|
||||
varsCtxToBuilder(&sb, ctx, indent)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
||||
sb.WriteString("funcs: {\n")
|
||||
first := true
|
||||
func (ctx *SimpleStore) funcsToDict(dict *DictType) *DictType {
|
||||
names := ctx.EnumFuncs(func(name string) bool { return true })
|
||||
slices.Sort(names)
|
||||
for _, name := range names {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
sb.WriteByte(',')
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
value, _ := ctx.GetFuncInfo(name)
|
||||
sb.WriteString(strings.Repeat("\t", indent+1))
|
||||
sb.WriteString(name)
|
||||
//sb.WriteString("=")
|
||||
if formatter, ok := value.(Formatter); ok {
|
||||
sb.WriteString(formatter.ToString(0))
|
||||
(*dict)[name] = formatter.ToString(0)
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", value))
|
||||
(*dict)[name] = fmt.Sprintf("%v", value)
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n}\n")
|
||||
return dict
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
varsCtxToBuilder(&sb, ctx, 0)
|
||||
funcsCtxToBuilder(&sb, ctx, 0)
|
||||
return sb.String()
|
||||
func (ctx *SimpleStore) 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) {
|
||||
@@ -114,6 +99,11 @@ func (ctx *SimpleStore) GetVar(varName string) (v any, exists bool) {
|
||||
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
|
||||
@@ -174,7 +164,7 @@ func (ctx *SimpleStore) EnumFuncs(acceptor func(name string) (accept bool)) (fun
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) Call(name string, args []any) (result any, err error) {
|
||||
if info, exists := ctx.funcStore[name]; exists {
|
||||
if info, exists := GetLocalFuncInfo(ctx, name); exists {
|
||||
functor := info.Functor()
|
||||
result, err = functor.Invoke(ctx, name, args)
|
||||
} else {
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// simple-var-store.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SimpleVarStore struct {
|
||||
varStore map[string]any
|
||||
}
|
||||
|
||||
func NewSimpleVarStore() *SimpleVarStore {
|
||||
return &SimpleVarStore{
|
||||
varStore: make(map[string]any),
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *SimpleVarStore) cloneVars() (vars map[string]any) {
|
||||
return CloneFilteredMap(ctx.varStore, func(name string) bool { return name[0] != '@' })
|
||||
}
|
||||
|
||||
func (ctx *SimpleVarStore) Clone() (clone ExprContext) {
|
||||
// fmt.Println("*** Cloning context ***")
|
||||
clone = &SimpleVarStore{
|
||||
varStore: ctx.cloneVars(),
|
||||
}
|
||||
return clone
|
||||
}
|
||||
|
||||
func (ctx *SimpleVarStore) GetVar(varName string) (v any, exists bool) {
|
||||
v, exists = ctx.varStore[varName]
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx *SimpleVarStore) UnsafeSetVar(varName string, value any) {
|
||||
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
|
||||
ctx.varStore[varName] = value
|
||||
}
|
||||
|
||||
func (ctx *SimpleVarStore) SetVar(varName string, value any) {
|
||||
// fmt.Printf("[%p] SetVar(%v, %v)\n", ctx, varName, value)
|
||||
if allowedValue, ok := fromGenericAny(value); ok {
|
||||
ctx.varStore[varName] = allowedValue
|
||||
} else {
|
||||
panic(fmt.Errorf("unsupported type %T of value %v", value, value))
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *SimpleVarStore) EnumVars(acceptor func(name string) (accept bool)) (varNames []string) {
|
||||
varNames = make([]string, 0)
|
||||
for name := range ctx.varStore {
|
||||
if acceptor != nil {
|
||||
if acceptor(name) {
|
||||
varNames = append(varNames, name)
|
||||
}
|
||||
} else {
|
||||
varNames = append(varNames, name)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx *SimpleVarStore) GetFuncInfo(name string) (f ExprFunc, exists bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx *SimpleVarStore) Call(name string, args []any) (result any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// func (ctx *SimpleVarStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
|
||||
// }
|
||||
func (ctx *SimpleVarStore) RegisterFuncInfo(info ExprFunc) {
|
||||
}
|
||||
func (ctx *SimpleVarStore) RegisterFunc2(name string, f Functor, returnType string, param []ExprFuncParam) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
|
||||
return
|
||||
}
|
||||
|
||||
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
||||
sb.WriteString("vars: {\n")
|
||||
first := true
|
||||
for _, name := range ctx.EnumVars(func(name string) bool { return name[0] != '_' }) {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
sb.WriteByte(',')
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
|
||||
value, _ := ctx.GetVar(name)
|
||||
sb.WriteString(strings.Repeat("\t", indent+1))
|
||||
sb.WriteString(name)
|
||||
sb.WriteString(": ")
|
||||
if f, ok := value.(Formatter); ok {
|
||||
sb.WriteString(f.ToString(0))
|
||||
} else if _, ok = value.(Functor); ok {
|
||||
sb.WriteString("func(){}")
|
||||
// } else if _, ok = value.(map[any]any); ok {
|
||||
// sb.WriteString("dict{}")
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", value))
|
||||
}
|
||||
}
|
||||
sb.WriteString(strings.Repeat("\t", indent))
|
||||
sb.WriteString("\n}\n")
|
||||
}
|
||||
|
||||
func varsCtxToString(ctx ExprContext, indent int) string {
|
||||
var sb strings.Builder
|
||||
varsCtxToBuilder(&sb, ctx, indent)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (ctx *SimpleVarStore) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
varsCtxToBuilder(&sb, ctx, 0)
|
||||
return sb.String()
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// t_bool_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBool(t *testing.T) {
|
||||
section := "Bool"
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`true`, true, nil},
|
||||
/* 2 */ {`false`, false, nil},
|
||||
/* 3 */ {`not false`, true, nil},
|
||||
/* 4 */ {`not 1`, false, nil},
|
||||
/* 5 */ {`not "true"`, false, nil},
|
||||
/* 6 */ {`not "false"`, false, nil},
|
||||
/* 7 */ {`not ""`, true, nil},
|
||||
/* 8 */ {`not []`, nil, errors.New(`[1:4] prefix/postfix operator "NOT" do not support operand '[]' [list]`)},
|
||||
/* 9 */ {`true and false`, false, nil},
|
||||
/* 10 */ {`true and []`, nil, errors.New(`[1:9] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "AND"`)},
|
||||
/* 11 */ {`[] and false`, nil, errors.New(`got list as left operand type of 'AND' operator, it must be bool`)},
|
||||
/* 12 */ {`true or false`, true, nil},
|
||||
/* 13 */ {`true or []`, true, nil},
|
||||
/* 14 */ {`[] or false`, nil, errors.New(`got list as left operand type of 'OR' operator, it must be bool`)},
|
||||
/* 13 */ //{`true or []`, nil, errors.New(`[1:8] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "OR"`)},
|
||||
}
|
||||
|
||||
// t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// runTestSuiteSpec(t, section, inputs, 1)
|
||||
runTestSuite(t, section, inputs)
|
||||
}
|
||||
|
||||
func TestBoolNoShortcut(t *testing.T) {
|
||||
section := "Bool-NoShortcut"
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`true`, true, nil},
|
||||
/* 2 */ {`false`, false, nil},
|
||||
/* 3 */ {`not false`, true, nil},
|
||||
/* 4 */ {`not 1`, false, nil},
|
||||
/* 5 */ {`not "true"`, false, nil},
|
||||
/* 6 */ {`not "false"`, false, nil},
|
||||
/* 7 */ {`not ""`, true, nil},
|
||||
/* 8 */ {`not []`, nil, errors.New(`[1:4] prefix/postfix operator "NOT" do not support operand '[]' [list]`)},
|
||||
/* 9 */ {`true and false`, false, nil},
|
||||
/* 10 */ {`true and []`, nil, errors.New(`[1:9] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "AND"`)},
|
||||
/* 11 */ {`[] and false`, nil, errors.New(`[1:7] left operand '[]' [list] and right operand 'false' [bool] are not compatible with operator "AND"`)},
|
||||
/* 12 */ {`true or false`, true, nil},
|
||||
/* 13 */ {`true or []`, nil, errors.New(`[1:8] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "OR"`)},
|
||||
/* 14 */ {`[] or false`, nil, errors.New(`[1:6] left operand '[]' [list] and right operand 'false' [bool] are not compatible with operator "OR"`)},
|
||||
}
|
||||
|
||||
// t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
ctx := NewSimpleStore()
|
||||
current := SetCtrl(ctx, ControlBoolShortcut, false)
|
||||
|
||||
// runCtxTestSuiteSpec(t, ctx, section, inputs, 1)
|
||||
runCtxTestSuite(t, ctx, section, inputs)
|
||||
|
||||
SetCtrl(ctx, ControlBoolShortcut, current)
|
||||
}
|
||||
@@ -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},
|
||||
@@ -23,7 +23,7 @@ func TestFuncBase(t *testing.T) {
|
||||
/* 8 */ {`int("432")`, int64(432), nil},
|
||||
/* 9 */ {`int("1.5")`, nil, errors.New(`strconv.Atoi: parsing "1.5": invalid syntax`)},
|
||||
/* 10 */ {`int("432", 4)`, nil, errors.New(`int(): too much params -- expected 1, got 2`)},
|
||||
/* 11 */ {`int(nil)`, nil, errors.New(`int(): can't convert <nil> to int`)},
|
||||
/* 11 */ {`int(nil)`, nil, errors.New(`int(): can't convert nil to int`)},
|
||||
/* 12 */ {`isInt(2+1)`, true, nil},
|
||||
/* 13 */ {`isInt(3.1)`, false, nil},
|
||||
/* 14 */ {`isFloat(3.1)`, true, nil},
|
||||
@@ -63,5 +63,5 @@ func TestFuncBase(t *testing.T) {
|
||||
t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// parserTestSpec(t, section, inputs, 2)
|
||||
parserTest(t, section, inputs)
|
||||
runTestSuite(t, section, inputs)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// t_builtin-fmt.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
// "errors"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFuncFmt(t *testing.T) {
|
||||
section := "Builtin-Fmt"
|
||||
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`builtin "fmt"; print("ciao")`, int64(4), nil},
|
||||
/* 2 */ {`builtin "fmt"; println(" ciao")`, int64(6), nil},
|
||||
}
|
||||
|
||||
//t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// runTestSuiteSpec(t, section, inputs, 19)
|
||||
runTestSuite(t, section, inputs)
|
||||
}
|
||||
|
||||
func TestFmt(t *testing.T) {
|
||||
section := "Builtin-Fmt"
|
||||
|
||||
text := "ciao mondo"
|
||||
inputs := []inputType{
|
||||
/* 1 */ {fmt.Sprintf(`println("%s")`, text), int64(11), nil},
|
||||
}
|
||||
|
||||
// t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
var b bytes.Buffer
|
||||
ctx := NewSimpleStore()
|
||||
currentStdout := SetCtrl(ctx, ControlStdout, &b)
|
||||
|
||||
runCtxTestSuite(t, ctx, section, inputs)
|
||||
|
||||
SetCtrl(ctx, ControlStdout, currentStdout)
|
||||
if b.String() != text+"\n" {
|
||||
t.Errorf("println(): Got: %q, Want: %q", b.String(), text+"\n")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
runTestSuite(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},
|
||||
@@ -25,5 +25,5 @@ func TestFuncMathArith(t *testing.T) {
|
||||
// t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// parserTestSpec(t, section, inputs, 69)
|
||||
parserTest(t, section, inputs)
|
||||
runTestSuite(t, section, inputs)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// 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`)},
|
||||
/* 8 */ {`builtin "os.file"; fileWriteText(nil, "bye")`, nil, errors.New(`fileWriteText(): invalid file handle`)},
|
||||
/* 9 */ {`builtin "os.file"; handle=fileOpen()`, nil, errors.New(`fileOpen(): too few params -- expected 1, got 0`)},
|
||||
/* 10 */ {`builtin "os.file"; handle=fileOpen(123)`, nil, errors.New(`fileOpen(): missing or invalid file path`)},
|
||||
/* 11 */ {`builtin "os.file"; handle=fileCreate(123)`, nil, errors.New(`fileCreate(): missing or invalid file path`)},
|
||||
/* 12 */ {`builtin "os.file"; handle=fileAppend(123)`, nil, errors.New(`fileAppend(): missing or invalid file path`)},
|
||||
/* 13 */ {`builtin "os.file"; handle=fileClose(123)`, nil, errors.New(`fileClose(): invalid file handle`)},
|
||||
/* 14 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); c=fileReadTextAll(handle); fileClose(handle); c`, "bye-bye", nil},
|
||||
/* 15 */ {`builtin "os.file"; c=fileReadTextAll(123)`, nil, errors.New(`fileReadTextAll(): invalid file handle 123 [int64]`)},
|
||||
}
|
||||
|
||||
// t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// parserTestSpec(t, section, inputs, 69)
|
||||
runTestSuite(t, section, inputs)
|
||||
}
|
||||
@@ -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", ".")
|
||||
|
||||
// runTestSuiteSpec(t, section, inputs, 19)
|
||||
runTestSuite(t, section, inputs)
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// t_common_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type inputType struct {
|
||||
source string
|
||||
wantResult any
|
||||
wantErr error
|
||||
}
|
||||
|
||||
func runCtxTestSuiteSpec(t *testing.T, ctx ExprContext, section string, inputs []inputType, spec ...int) {
|
||||
succeeded := 0
|
||||
failed := 0
|
||||
for _, count := range spec {
|
||||
good := doTest(t, ctx, section, &inputs[count-1], count)
|
||||
|
||||
if good {
|
||||
succeeded++
|
||||
} else {
|
||||
failed++
|
||||
}
|
||||
}
|
||||
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(spec), succeeded, failed)
|
||||
}
|
||||
|
||||
func runTestSuiteSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
|
||||
runCtxTestSuiteSpec(t, nil, section, inputs, spec...)
|
||||
}
|
||||
|
||||
func runCtxTestSuite(t *testing.T, ctx ExprContext, section string, inputs []inputType) {
|
||||
|
||||
succeeded := 0
|
||||
failed := 0
|
||||
|
||||
for i, input := range inputs {
|
||||
good := doTest(t, ctx, section, &input, i+1)
|
||||
if good {
|
||||
succeeded++
|
||||
} else {
|
||||
failed++
|
||||
}
|
||||
}
|
||||
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||
}
|
||||
func runTestSuite(t *testing.T, section string, inputs []inputType) {
|
||||
runCtxTestSuite(t, nil, section, inputs)
|
||||
/*
|
||||
succeeded := 0
|
||||
failed := 0
|
||||
|
||||
for i, input := range inputs {
|
||||
good := doTest(t, nil, section, &input, i+1)
|
||||
if good {
|
||||
succeeded++
|
||||
} else {
|
||||
failed++
|
||||
}
|
||||
}
|
||||
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||
*/
|
||||
}
|
||||
|
||||
func doTest(t *testing.T, ctx ExprContext, section string, input *inputType, count int) (good bool) {
|
||||
var expr Expr
|
||||
var gotResult any
|
||||
var gotErr error
|
||||
|
||||
parser := NewParser()
|
||||
if ctx == nil {
|
||||
ctx = NewSimpleStore()
|
||||
}
|
||||
|
||||
logTest(t, count, section, input.source, input.wantResult, input.wantErr)
|
||||
|
||||
r := strings.NewReader(input.source)
|
||||
scanner := NewScanner(r, DefaultTranslations())
|
||||
|
||||
good = true
|
||||
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
|
||||
gotResult, gotErr = expr.Eval(ctx)
|
||||
}
|
||||
|
||||
eq := reflect.DeepEqual(gotResult, input.wantResult)
|
||||
|
||||
if !eq /*gotResult != input.wantResult*/ {
|
||||
t.Errorf("%d: %q -> result = %v [%s], want = %v [%s]", count, input.source, gotResult, TypeName(gotResult), input.wantResult, TypeName(input.wantResult))
|
||||
good = false
|
||||
}
|
||||
|
||||
if gotErr != input.wantErr {
|
||||
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
|
||||
t.Errorf("%d: %q -> got-err = <%v>, expected-err = <%v>", count, input.source, gotErr, input.wantErr)
|
||||
good = false
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
|
||||
if wantErr == nil {
|
||||
t.Logf("[+]%s nr %3d -- %q --> %v", section, n, source, wantResult)
|
||||
} else {
|
||||
t.Logf("[-]%s nr %3d -- %q --> %v", section, n, source, wantErr)
|
||||
}
|
||||
}
|
||||
+6
-2
@@ -28,6 +28,10 @@ func TestDictParser(t *testing.T) {
|
||||
/* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil},
|
||||
/* 6 */ {`{1:"one"} + {2:"two"}`, map[any]any{1: "one", 2: "two"}, nil},
|
||||
/* 7 */ {`2 in {1:"one", 2:"two"}`, true, nil},
|
||||
/* 8 */ {`D={"a":1, "b":2}; D["a"]=9; D`, map[any]any{"a": 9, "b": 2}, nil},
|
||||
/* 9 */ {`D={"a":1, "b":2}; D["z"]=9; D`, map[any]any{"z": 9, "a": 1, "b": 2}, nil},
|
||||
/* 10 */ {`D={"a":1, "b":2}; D[nil]=9`, nil, errors.New(`[1:21] index/key is nil`)},
|
||||
/* 11 */ {`D={"a":1, "b":2}; D["a"]`, int64(1), nil},
|
||||
}
|
||||
|
||||
succeeded := 0
|
||||
@@ -46,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)
|
||||
|
||||
@@ -107,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)),
|
||||
|
||||
+2
-2
@@ -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 */ {`
|
||||
@@ -33,5 +33,5 @@ func TestExpr(t *testing.T) {
|
||||
// t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// parserTestSpec(t, section, inputs, 3)
|
||||
parserTest(t, section, inputs)
|
||||
runTestSuite(t, section, inputs)
|
||||
}
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ func TestFractionsParser(t *testing.T) {
|
||||
/* 20 */ {`fract("1a")`, (*FractionType)(nil), errors.New(`strconv.ParseInt: parsing "1a": invalid syntax`)},
|
||||
/* 21 */ {`fract(1,0)`, nil, errors.New(`fract(): division by zero`)},
|
||||
}
|
||||
parserTest(t, section, inputs)
|
||||
runTestSuite(t, section, inputs)
|
||||
}
|
||||
|
||||
func TestFractionToStringSimple(t *testing.T) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// t_func-string_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFuncString(t *testing.T) {
|
||||
section := "Func-String"
|
||||
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`builtin "string"; joinStr("-", "one", "two", "three")`, "one-two-three", nil},
|
||||
/* 2 */ {`builtin "string"; joinStr("-", ["one", "two", "three"])`, "one-two-three", nil},
|
||||
/* 3 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr("-", ls)`, "one-two-three", nil},
|
||||
/* 4 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr(1, ls)`, nil, errors.New(`joinStr() the "separator" parameter must be a string, got a int64 (1)`)},
|
||||
/* 5 */ {`builtin "string"; ls= ["one", 2, "three"]; joinStr("-", ls)`, nil, errors.New(`joinStr() expected string, got int64 (2)`)},
|
||||
/* 6 */ {`builtin "string"; "<"+trimStr(" bye bye ")+">"`, "<bye bye>", nil},
|
||||
/* 7 */ {`builtin "string"; subStr("0123456789", 1,2)`, "12", nil},
|
||||
/* 8 */ {`builtin "string"; subStr("0123456789", -3,2)`, "78", nil},
|
||||
/* 9 */ {`builtin "string"; subStr("0123456789", -3)`, "789", nil},
|
||||
/* 10 */ {`builtin "string"; subStr("0123456789")`, "0123456789", nil},
|
||||
/* 11 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "012")`, true, nil},
|
||||
/* 12 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "0125")`, false, nil},
|
||||
/* 13 */ {`builtin "string"; startsWithStr("0123456789")`, nil, errors.New(`startsWithStr(): too few params -- expected 2 or more, got 1`)},
|
||||
/* 14 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "789")`, true, nil},
|
||||
/* 15 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "0125")`, false, nil},
|
||||
/* 16 */ {`builtin "string"; endsWithStr("0123456789")`, nil, errors.New(`endsWithStr(): too few params -- expected 2 or more, got 1`)},
|
||||
/* 17 */ {`builtin "string"; splitStr("one-two-three", "-")`, newListA("one", "two", "three"), nil},
|
||||
/* 18 */ {`builtin "string"; joinStr("-", [1, "two", "three"])`, nil, errors.New(`joinStr() expected string, got int64 (1)`)},
|
||||
/* 19 */ {`builtin "string"; joinStr()`, nil, errors.New(`joinStr(): too few params -- expected 1 or more, got 0`)},
|
||||
|
||||
/* 69 */ /*{`builtin "string"; $$global`, `vars: {
|
||||
}
|
||||
funcs: {
|
||||
add(any=0 ...) -> number,
|
||||
dec(any) -> decimal,
|
||||
endsWithStr(source, suffix) -> boolean,
|
||||
fract(any, denominator=1) -> fraction,
|
||||
import( ...) -> any,
|
||||
importAll( ...) -> any,
|
||||
int(any) -> integer,
|
||||
isBool(any) -> boolean,
|
||||
isDec(any) -> boolean,
|
||||
isDict(any) -> boolean,
|
||||
isFloat(any) -> boolean,
|
||||
isFract(any) -> boolean,
|
||||
isInt(any) -> boolean,
|
||||
isList(any) -> boolean,
|
||||
isNil(any) -> boolean,
|
||||
isString(any) -> boolean,
|
||||
joinStr(separator, item="" ...) -> string,
|
||||
mul(any=1 ...) -> number,
|
||||
splitStr(source, separator="", count=-1) -> list of string,
|
||||
startsWithStr(source, prefix) -> boolean,
|
||||
subStr(source, start=0, count=-1) -> string,
|
||||
trimStr(source) -> string
|
||||
}
|
||||
`, nil},*/
|
||||
}
|
||||
|
||||
//t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// parserTestSpec(t, "Func", inputs, 69)
|
||||
parserTest(t, section, inputs)
|
||||
}
|
||||
+8
-5
@@ -29,12 +29,15 @@ func TestFuncs(t *testing.T) {
|
||||
/* 15 */ {`f=func(x,n=2){x+n}; f(3)`, int64(5), nil},
|
||||
/* 16 */ {`f=func(x,n=2,y){x+n}`, nil, errors.New(`[1:16] can't mix default and non-default parameters`)},
|
||||
/* 17 */ {`f=func(x,n){1}; f(3,4,)`, nil, errors.New(`[1:24] expected "function-param-value", got ")"`)},
|
||||
/* 18 */ {`factory=func(base){func(){@base=base+1}}; inc10=factory(10); inc5=factory(5); inc10(); inc5(); inc10()`, int64(12), nil},
|
||||
/* 19 */ {`f=func(a,y=1,z="sos"){}; string(f)`, `f(a, y=1, z="sos"):any{}`, nil},
|
||||
// /* 18 */ {`f=func(a){a*2}`, nil, errors.New(`[1:24] expected "function-param-value", got ")"`)},
|
||||
}
|
||||
|
||||
// t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// parserTestSpec(t, section, inputs, 17)
|
||||
parserTest(t, section, inputs)
|
||||
// runTestSuiteSpec(t, section, inputs, 17)
|
||||
runTestSuite(t, section, inputs)
|
||||
}
|
||||
|
||||
func dummy(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
@@ -42,8 +45,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)
|
||||
@@ -51,7 +54,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 {
|
||||
|
||||
+1
-1
@@ -23,5 +23,5 @@ func TestCollections(t *testing.T) {
|
||||
t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// parserTestSpec(t, section, inputs, 5)
|
||||
parserTest(t, section, inputs)
|
||||
runTestSuite(t, section, inputs)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+10
-10
@@ -8,19 +8,19 @@ 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{
|
||||
// /* 1 */ {`0?{}`, nil, nil},
|
||||
// }
|
||||
parserTest(t, "Iterator", inputs)
|
||||
runTestSuite(t, "Iterator", inputs)
|
||||
}
|
||||
|
||||
+24
-91
@@ -6,33 +6,26 @@ 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 */ {`[]`, []any{}, nil},
|
||||
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
||||
/* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil},
|
||||
/* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil},
|
||||
/* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
||||
/* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil},
|
||||
/* 1 */ {`[]`, newListA(), nil},
|
||||
/* 2 */ {`[1,2,3]`, newListA(int64(1), int64(2), int64(3)), nil},
|
||||
/* 3 */ {`[1,2,"hello"]`, newListA(int64(1), int64(2), "hello"), nil},
|
||||
/* 4 */ {`[1+2, not true, "hello"]`, newListA(int64(3), false, "hello"), nil},
|
||||
/* 5 */ {`[1,2]+[3]`, newListA(int64(1), int64(2), int64(3)), nil},
|
||||
/* 6 */ {`[1,4,3,2]-[3]`, newListA(int64(1), int64(4), int64(2)), nil},
|
||||
/* 7 */ {`add([1,4,3,2])`, int64(10), nil},
|
||||
/* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
|
||||
/* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
|
||||
/* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 (2 in 1) has wrong type string, number expected`)},
|
||||
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
|
||||
/* 12 */ {`[1,2,3] << 2+2`, []any{int64(1), int64(2), int64(3), int64(4)}, nil},
|
||||
/* 13 */ {`2-1 >> [2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
||||
/* 12 */ {`[1,2,3] << 2+2`, newListA(int64(1), int64(2), int64(3), int64(4)), nil},
|
||||
/* 13 */ {`2-1 >> [2,3]`, newListA(int64(1), int64(2), int64(3)), nil},
|
||||
/* 14 */ {`[1,2,3][1]`, int64(2), nil},
|
||||
/* 15 */ {`ls=[1,2,3] but ls[1]`, int64(2), nil},
|
||||
/* 16 */ {`ls=[1,2,3] but ls[-1]`, int64(3), nil},
|
||||
@@ -41,89 +34,29 @@ func TestListParser(t *testing.T) {
|
||||
/* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil},
|
||||
/* 20 */ {`#["a", "b", "c"]`, int64(3), nil},
|
||||
/* 21 */ {`"b" in ["a", "b", "c"]`, true, nil},
|
||||
/* 22 */ {`a=[1,2]; (a)<<3`, []any{1, 2, 3}, nil},
|
||||
/* 23 */ {`a=[1,2]; (a)<<3; 1`, []any{1, 2}, nil},
|
||||
/* 22 */ {`a=[1,2]; (a)<<3`, newListA(int64(1), int64(2), int64(3)), nil},
|
||||
/* 23 */ {`a=[1,2]; (a)<<3; a`, newListA(int64(1), int64(2)), nil},
|
||||
/* 24 */ {`["a","b","c","d"][1]`, "b", nil},
|
||||
/* 25 */ {`["a","b","c","d"][1,1]`, nil, errors.New(`[1:19] one index only is allowed`)},
|
||||
/* 26 */ {`[0,1,2,3,4][:]`, ListType{int64(0), int64(1), int64(2), int64(3), int64(4)}, nil},
|
||||
/* 26 */ {`[0,1,2,3,4][:]`, newListA(int64(0), int64(1), int64(2), int64(3), int64(4)), nil},
|
||||
/* 27 */ {`["a", "b", "c"] << ;`, nil, errors.New(`[1:18] infix operator "<<" requires two non-nil operands, got 1`)},
|
||||
/* 28 */ {`2 << 3;`, nil, errors.New(`[1:4] left operand '2' [integer] and right operand '3' [integer] are not compatible with operator "<<"`)},
|
||||
/* 29 */ {`but >> ["a", "b", "c"]`, nil, errors.New(`[1:6] infix operator ">>" requires two non-nil operands, got 0`)},
|
||||
/* 30 */ {`2 >> 3;`, nil, errors.New(`[1:4] left operand '2' [integer] and right operand '3' [integer] are not compatible with operator ">>"`)},
|
||||
/* 31 */ {`a=[1,2]; a<<3`, []any{1, 2, 3}, nil},
|
||||
/* 33 */ {`a=[1,2]; 5>>a`, []any{5, 1, 2}, nil},
|
||||
|
||||
// /* 8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, nil},
|
||||
// /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil},
|
||||
/* 31 */ {`a=[1,2]; a<<3`, newListA(int64(1), int64(2), int64(3)), nil},
|
||||
/* 33 */ {`a=[1,2]; 5>>a`, newListA(int64(5), int64(1), int64(2)), nil},
|
||||
/* 34 */ {`L=[1,2]; L[0]=9; L`, newListA(int64(9), int64(2)), nil},
|
||||
/* 35 */ {`L=[1,2]; L[5]=9; L`, nil, errors.New(`index 5 out of bounds (0, 1)`)},
|
||||
/* 36 */ {`L=[1,2]; L[]=9; L`, nil, errors.New(`[1:12] index/key specification expected, got [] [list]`)},
|
||||
/* 37 */ {`L=[1,2]; L[nil]=9;`, nil, errors.New(`[1:12] index/key is nil`)},
|
||||
/* 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},
|
||||
}
|
||||
|
||||
succeeded := 0
|
||||
failed := 0
|
||||
// t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// 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)
|
||||
// parserTestSpec(t, section, inputs, 17)
|
||||
runTestSuite(t, section, inputs)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
+6
-85
@@ -6,18 +6,12 @@ package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type inputType struct {
|
||||
source string
|
||||
wantResult any
|
||||
wantErr error
|
||||
}
|
||||
|
||||
func TestGeneralParser(t *testing.T) {
|
||||
section := "Parser"
|
||||
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`1+/*5*/2`, int64(3), nil},
|
||||
/* 2 */ {`3 == 4`, false, nil},
|
||||
@@ -115,8 +109,8 @@ func TestGeneralParser(t *testing.T) {
|
||||
/* 94 */ {`false or true`, true, nil},
|
||||
/* 95 */ {`false or (x==2)`, nil, errors.New(`undefined variable or function "x"`)},
|
||||
/* 96 */ {`a=5; a`, int64(5), nil},
|
||||
/* 97 */ {`2=5`, nil, errors.New(`assign operator ("=") must be preceded by a variable`)},
|
||||
/* 98 */ {`2+a=5`, nil, errors.New(`[1:3] left operand of "=" must be a variable`)},
|
||||
/* 97 */ {`2=5`, nil, errors.New(`[1:2] left operand of "=" must be a variable or a collection's item`)},
|
||||
/* 98 */ {`2+a=5`, nil, errors.New(`[1:3] left operand of "=" must be a variable or a collection's item`)},
|
||||
/* 99 */ {`2+(a=5)`, int64(7), nil},
|
||||
/* 100 */ {`x ?? "default"`, "default", nil},
|
||||
/* 101 */ {`x="hello"; x ?? "default"`, "hello", nil},
|
||||
@@ -145,79 +139,6 @@ func TestGeneralParser(t *testing.T) {
|
||||
}
|
||||
|
||||
// t.Setenv("EXPR_PATH", ".")
|
||||
// parserTestSpec(t, "General", inputs, 102)
|
||||
parserTest(t, "General", inputs)
|
||||
}
|
||||
|
||||
func parserTestSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
|
||||
succeeded := 0
|
||||
failed := 0
|
||||
for _, count := range spec {
|
||||
good := doTest(t, section, &inputs[count-1], count)
|
||||
|
||||
if good {
|
||||
succeeded++
|
||||
} else {
|
||||
failed++
|
||||
}
|
||||
}
|
||||
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(spec), succeeded, failed)
|
||||
}
|
||||
|
||||
func parserTest(t *testing.T, section string, inputs []inputType) {
|
||||
|
||||
succeeded := 0
|
||||
failed := 0
|
||||
|
||||
for i, input := range inputs {
|
||||
good := doTest(t, section, &input, i+1)
|
||||
if good {
|
||||
succeeded++
|
||||
} else {
|
||||
failed++
|
||||
}
|
||||
}
|
||||
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||
}
|
||||
|
||||
func doTest(t *testing.T, section string, input *inputType, count int) (good bool) {
|
||||
var expr Expr
|
||||
var gotResult any
|
||||
var gotErr error
|
||||
|
||||
ctx := NewSimpleStore()
|
||||
parser := NewParser(ctx)
|
||||
|
||||
logTest(t, count, section, input.source, input.wantResult, input.wantErr)
|
||||
|
||||
r := strings.NewReader(input.source)
|
||||
scanner := NewScanner(r, DefaultTranslations())
|
||||
|
||||
good = true
|
||||
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
|
||||
gotResult, gotErr = expr.Eval(ctx)
|
||||
}
|
||||
|
||||
eq := reflect.DeepEqual(gotResult, input.wantResult)
|
||||
|
||||
if !eq /*gotResult != input.wantResult*/ {
|
||||
t.Errorf("%d: %q -> result = %v [%T], want = %v [%T]", count, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
||||
good = false
|
||||
}
|
||||
|
||||
if gotErr != input.wantErr {
|
||||
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
|
||||
t.Errorf("%d: %q -> got-err = <%v>, expected-err = <%v>", count, input.source, gotErr, input.wantErr)
|
||||
good = false
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
|
||||
if wantErr == nil {
|
||||
t.Logf("[+]%s nr %3d -- %q --> %v", section, n, source, wantResult)
|
||||
} else {
|
||||
t.Logf("[-]%s nr %3d -- %q --> %v", section, n, source, wantErr)
|
||||
}
|
||||
// parserTestSpec(t, section, inputs, 102)
|
||||
runTestSuite(t, section, inputs)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// t_plugin_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func _TestImportPlugin(t *testing.T) {
|
||||
if err := importPlugin([]string{"test-resources"}, "json"); err != nil {
|
||||
t.Errorf("importPlugin() failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluginExists(t *testing.T) {
|
||||
name := "json"
|
||||
exists := pluginExists(name)
|
||||
t.Logf("pluginExists(%v): %v", name, exists)
|
||||
|
||||
}
|
||||
|
||||
func TestMakePluginName(t *testing.T) {
|
||||
name := "json"
|
||||
want := "expr-" + name + "-plugin.so"
|
||||
|
||||
if got := makePluginName(name); got != want {
|
||||
t.Errorf("makePluginName(%q) failed: Got: %q, Want: %q", name, got, want)
|
||||
}
|
||||
}
|
||||
@@ -48,5 +48,5 @@ func TestRelational(t *testing.T) {
|
||||
// t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// parserTestSpec(t, section, inputs, 31)
|
||||
parserTest(t, section, inputs)
|
||||
runTestSuite(t, section, inputs)
|
||||
}
|
||||
|
||||
+1
-1
@@ -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},
|
||||
|
||||
+1
-1
@@ -17,5 +17,5 @@ func TestStringsParser(t *testing.T) {
|
||||
/* 5 */ {`"abc"[1]`, `b`, nil},
|
||||
/* 6 */ {`#"abc"`, int64(3), nil},
|
||||
}
|
||||
parserTest(t, "String", inputs)
|
||||
runTestSuite(t, "String", inputs)
|
||||
}
|
||||
|
||||
+2
-2
@@ -16,6 +16,6 @@ func TestSomething(t *testing.T) {
|
||||
|
||||
// t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// parserTestSpec(t, section, inputs, 1)
|
||||
parserTest(t, section, inputs)
|
||||
// runTestSuiteSpec(t, section, inputs, 1)
|
||||
runTestSuite(t, section, inputs)
|
||||
}
|
||||
|
||||
+15
-5
@@ -6,6 +6,7 @@ package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -63,7 +64,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 +73,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 +82,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 +98,7 @@ func TestToIntOk(t *testing.T) {
|
||||
wantValue := int(64)
|
||||
wantErr := error(nil)
|
||||
|
||||
gotValue, gotErr := toInt(source, "test")
|
||||
gotValue, gotErr := ToGoInt(source, "test")
|
||||
|
||||
if gotErr != nil || gotValue != wantValue {
|
||||
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
|
||||
@@ -110,7 +111,7 @@ func TestToIntErr(t *testing.T) {
|
||||
wantValue := 0
|
||||
wantErr := errors.New(`test expected integer, got uint64 (64)`)
|
||||
|
||||
gotValue, gotErr := toInt(source, "test")
|
||||
gotValue, gotErr := ToGoInt(source, "test")
|
||||
|
||||
if gotErr.Error() != wantErr.Error() || gotValue != wantValue {
|
||||
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
|
||||
@@ -154,3 +155,12 @@ func TestAnyInteger(t *testing.T) {
|
||||
}
|
||||
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||
}
|
||||
|
||||
func TestCopyMap(t *testing.T) {
|
||||
source := map[string]int{"one": 1, "two": 2, "three": 3}
|
||||
dest := make(map[string]int)
|
||||
result := CopyMap(dest, source)
|
||||
if !reflect.DeepEqual(result, source) {
|
||||
t.Errorf("utils.CopyMap() failed")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 %T (%v)", valueDescription, computedValue, computedValue)
|
||||
err = self.Errorf("%s, got %s (%v)", valueDescription, TypeName(computedValue), computedValue)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
|
||||
leftType := getTypeName(leftValue)
|
||||
leftType := TypeName(leftValue)
|
||||
leftText := getFormatted(leftValue, Truncate)
|
||||
rightType := getTypeName(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",
|
||||
@@ -175,8 +175,8 @@ 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' [%T]",
|
||||
self.source(), value, value)
|
||||
"prefix/postfix operator %q do not support operand '%v' [%s]",
|
||||
self.source(), value, TypeName(value))
|
||||
}
|
||||
|
||||
func (self *term) Errorf(template string, args ...any) (err error) {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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)
|
||||
@@ -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 {
|
||||
|
||||
@@ -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:
|
||||
@@ -135,23 +135,25 @@ func anyInteger(v any) (i int64, ok bool) {
|
||||
}
|
||||
|
||||
func fromGenericAny(v any) (exprAny any, ok bool) {
|
||||
if exprAny, ok = v.(bool); ok {
|
||||
return
|
||||
}
|
||||
if exprAny, ok = v.(string); ok {
|
||||
return
|
||||
}
|
||||
if exprAny, ok = anyInteger(v); ok {
|
||||
return
|
||||
}
|
||||
if exprAny, ok = anyFloat(v); ok {
|
||||
return
|
||||
}
|
||||
if exprAny, ok = v.(*DictType); ok {
|
||||
return
|
||||
}
|
||||
if exprAny, ok = v.(*ListType); ok {
|
||||
return
|
||||
if v != nil {
|
||||
if exprAny, ok = v.(bool); ok {
|
||||
return
|
||||
}
|
||||
if exprAny, ok = v.(string); ok {
|
||||
return
|
||||
}
|
||||
if exprAny, ok = anyInteger(v); ok {
|
||||
return
|
||||
}
|
||||
if exprAny, ok = anyFloat(v); ok {
|
||||
return
|
||||
}
|
||||
if exprAny, ok = v.(*DictType); ok {
|
||||
return
|
||||
}
|
||||
if exprAny, ok = v.(*ListType); ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -176,10 +178,10 @@ func CopyMap[K comparable, V any](dest, source map[K]V) map[K]V {
|
||||
return dest
|
||||
}
|
||||
|
||||
func CloneMap[K comparable, V any](source map[K]V) map[K]V {
|
||||
dest := make(map[K]V, len(source))
|
||||
return CopyMap(dest, source)
|
||||
}
|
||||
// func CloneMap[K comparable, V any](source map[K]V) map[K]V {
|
||||
// dest := make(map[K]V, len(source))
|
||||
// return CopyMap(dest, source)
|
||||
// }
|
||||
|
||||
func CopyFilteredMap[K comparable, V any](dest, source map[K]V, filter func(key K) (accept bool)) map[K]V {
|
||||
// fmt.Printf("--- Clone with filter %p\n", filter)
|
||||
@@ -201,11 +203,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 ToGoInt(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 %T (%v)", description, value, value)
|
||||
err = fmt.Errorf("%s expected integer, got %s (%v)", description, TypeName(value), value)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user