Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 9bba40f155 | |||
| f66cd1fdb1 | |||
| f41ea96d17 | |||
| d84e690ef3 | |||
| 4b25a07699 | |||
| 3736214c5a | |||
| 78cbb7b36f | |||
| 2c87d6bf9e | |||
| 691c213d17 | |||
| fa136cb70b | |||
| 76dd01afcd | |||
| 4283fab816 | |||
| 03d4c192c2 | |||
| e5f63c3d9d | |||
| d545a35acf | |||
| e4275e2cb6 | |||
| 1ff5770264 | |||
| ba32e2dccf |
@@ -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 {
|
||||
@@ -127,9 +120,10 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
result, err = self.root.compute(ctx)
|
||||
if result, err = self.root.compute(ctx); err == nil {
|
||||
ctx.UnsafeSetVar(ControlLastResult, result)
|
||||
}
|
||||
}
|
||||
// } else {
|
||||
// err = errors.New("empty expression")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// function.go
|
||||
package expr
|
||||
|
||||
// ---- Linking with Expr functions
|
||||
type exprFunctor struct {
|
||||
baseFunctor
|
||||
params []ExprFuncParam
|
||||
expr Expr
|
||||
defCtx ExprContext
|
||||
}
|
||||
|
||||
// func newExprFunctor(e Expr, params []string, ctx ExprContext) *exprFunctor {
|
||||
// return &exprFunctor{expr: e, params: params, defCtx: ctx}
|
||||
// }
|
||||
|
||||
func newExprFunctor(e Expr, params []ExprFuncParam, ctx ExprContext) *exprFunctor {
|
||||
return &exprFunctor{expr: e, params: params, defCtx: ctx}
|
||||
}
|
||||
|
||||
func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
if functor.defCtx != nil {
|
||||
ctx.Merge(functor.defCtx)
|
||||
}
|
||||
|
||||
for i, p := range functor.params {
|
||||
if i < len(args) {
|
||||
arg := args[i]
|
||||
if funcArg, ok := arg.(Functor); ok {
|
||||
// ctx.RegisterFunc(p, functor, 0, -1)
|
||||
paramSpecs := funcArg.GetParams()
|
||||
ctx.RegisterFunc(p.Name(), funcArg, TypeAny, paramSpecs)
|
||||
} else {
|
||||
ctx.UnsafeSetVar(p.Name(), arg)
|
||||
}
|
||||
} else {
|
||||
ctx.UnsafeSetVar(p.Name(), nil)
|
||||
}
|
||||
}
|
||||
result, err = functor.expr.Eval(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
func CallExprFunction(parentCtx ExprContext, funcName string, params ...any) (v any, err error) {
|
||||
ctx := cloneContext(parentCtx)
|
||||
ctx.SetParent(parentCtx)
|
||||
|
||||
if err == nil {
|
||||
if err = checkFunctionCall(ctx, funcName, ¶ms); err == nil {
|
||||
if v, err = ctx.Call(funcName, params); err == nil {
|
||||
exportObjects(parentCtx, 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,7 +1,7 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// func-builtins.go
|
||||
// builtin-base.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
@@ -54,6 +54,24 @@ func isDictionaryFunc(ctx ExprContext, name string, args []any) (result any, err
|
||||
return
|
||||
}
|
||||
|
||||
func boolFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
switch v := args[0].(type) {
|
||||
case int64:
|
||||
result = (v != 0)
|
||||
case *FractionType:
|
||||
result = v.num != 0
|
||||
case float64:
|
||||
result = v != 0.0
|
||||
case bool:
|
||||
result = v
|
||||
case string:
|
||||
result = len(v) > 0
|
||||
default:
|
||||
err = ErrCantConvert(name, v, "bool")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
switch v := args[0].(type) {
|
||||
case int64:
|
||||
@@ -72,7 +90,7 @@ func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
result = int64(i)
|
||||
}
|
||||
default:
|
||||
err = errCantConvert(name, v, "int")
|
||||
err = ErrCantConvert(name, v, "int")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -94,10 +112,10 @@ func decFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
if f, err = strconv.ParseFloat(v, 64); err == nil {
|
||||
result = f
|
||||
}
|
||||
case *fraction:
|
||||
case *FractionType:
|
||||
result = v.toFloat()
|
||||
default:
|
||||
err = errCantConvert(name, v, "float")
|
||||
err = ErrCantConvert(name, v, "float")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -109,9 +127,9 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
|
||||
if len(args) > 1 {
|
||||
var ok bool
|
||||
if den, ok = args[1].(int64); !ok {
|
||||
err = errExpectedGot(name, "integer", args[1])
|
||||
err = ErrExpectedGot(name, "integer", args[1])
|
||||
} else if den == 0 {
|
||||
err = errDivisionByZero(name)
|
||||
err = ErrFuncDivisionByZero(name)
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
@@ -119,10 +137,6 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
|
||||
}
|
||||
case float64:
|
||||
result, err = float64ToFraction(v)
|
||||
// var n, d int64
|
||||
// if n, d, err = float64ToFraction(v); err == nil {
|
||||
// result = newFraction(n, d)
|
||||
// }
|
||||
case bool:
|
||||
if v {
|
||||
result = newFraction(1, 1)
|
||||
@@ -131,20 +145,10 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
|
||||
}
|
||||
case string:
|
||||
result, err = makeGeneratingFraction(v)
|
||||
// var f float64
|
||||
// // TODO temporary implementation
|
||||
// if f, err = strconv.ParseFloat(v, 64); err == nil {
|
||||
// var n, d int64
|
||||
// if n, d, err = float64ToFraction(f); err == nil {
|
||||
// result = newFraction(n, d)
|
||||
// }
|
||||
// } else {
|
||||
// errors.New("convertion from string to float is ongoing")
|
||||
// }
|
||||
case *fraction:
|
||||
case *FractionType:
|
||||
result = v
|
||||
default:
|
||||
err = errCantConvert(name, v, "float")
|
||||
err = ErrCantConvert(name, v, "float")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -154,22 +158,29 @@ func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err err
|
||||
}
|
||||
|
||||
func ImportBuiltinsFuncs(ctx ExprContext) {
|
||||
ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, 1)
|
||||
ctx.RegisterFunc("isInt", &simpleFunctor{f: isIntFunc}, 1, 1)
|
||||
ctx.RegisterFunc("isFloat", &simpleFunctor{f: isFloatFunc}, 1, 1)
|
||||
ctx.RegisterFunc("isBool", &simpleFunctor{f: isBoolFunc}, 1, 1)
|
||||
ctx.RegisterFunc("isString", &simpleFunctor{f: isStringFunc}, 1, 1)
|
||||
ctx.RegisterFunc("isFraction", &simpleFunctor{f: isFractionFunc}, 1, 1)
|
||||
ctx.RegisterFunc("isFract", &simpleFunctor{f: isFractionFunc}, 1, 1)
|
||||
ctx.RegisterFunc("isRational", &simpleFunctor{f: isRationalFunc}, 1, 1)
|
||||
ctx.RegisterFunc("isList", &simpleFunctor{f: isListFunc}, 1, 1)
|
||||
ctx.RegisterFunc("isDictionary", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
|
||||
ctx.RegisterFunc("isDict", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
|
||||
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, 1)
|
||||
ctx.RegisterFunc("dec", &simpleFunctor{f: decFunc}, 1, 1)
|
||||
ctx.RegisterFunc("fract", &simpleFunctor{f: fractFunc}, 1, 2)
|
||||
anyParams := []ExprFuncParam{
|
||||
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("bool", NewGolangFunctor(boolFunc), TypeBoolean, anyParams)
|
||||
ctx.RegisterFunc("int", NewGolangFunctor(intFunc), TypeInt, anyParams)
|
||||
ctx.RegisterFunc("dec", NewGolangFunctor(decFunc), TypeFloat, anyParams)
|
||||
ctx.RegisterFunc("fract", NewGolangFunctor(fractFunc), TypeFraction, []ExprFuncParam{
|
||||
NewFuncParam(ParamValue),
|
||||
NewFuncParamFlagDef("denominator", PfDefault, 1),
|
||||
})
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerImport("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
|
||||
RegisterBuiltinModule("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// builtin-fmt.go
|
||||
package expr
|
||||
|
||||
import "fmt"
|
||||
|
||||
func printFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var n int
|
||||
if n, err = fmt.Print(args...); err == nil {
|
||||
result = int64(n)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func printLnFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var n int
|
||||
if n, err = fmt.Println(args...); err == nil {
|
||||
result = int64(n)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ImportFmtFuncs(ctx ExprContext) {
|
||||
ctx.RegisterFunc("print", NewGolangFunctor(printFunc), TypeInt, []ExprFuncParam{
|
||||
NewFuncParamFlag(ParamItem, PfRepeat),
|
||||
})
|
||||
ctx.RegisterFunc("println", NewGolangFunctor(printLnFunc), TypeInt, []ExprFuncParam{
|
||||
NewFuncParamFlag(ParamItem, PfRepeat),
|
||||
})
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterBuiltinModule("fmt", ImportFmtFuncs, "String and console formatting functions")
|
||||
}
|
||||
@@ -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 (
|
||||
@@ -21,7 +21,7 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
|
||||
var sumAsFloat, sumAsFract bool
|
||||
var floatSum float64 = 0.0
|
||||
var intSum int64 = 0
|
||||
var fractSum *fraction
|
||||
var fractSum *FractionType
|
||||
var v any
|
||||
|
||||
level++
|
||||
@@ -61,9 +61,9 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
|
||||
if sumAsFloat {
|
||||
floatSum += numAsFloat(v)
|
||||
} else if sumAsFract {
|
||||
var item *fraction
|
||||
var item *FractionType
|
||||
var ok bool
|
||||
if item, ok = v.(*fraction); !ok {
|
||||
if item, ok = v.(*FractionType); !ok {
|
||||
iv, _ := v.(int64)
|
||||
item = newFraction(iv, 1)
|
||||
}
|
||||
@@ -95,7 +95,7 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
|
||||
var mulAsFloat, mulAsFract bool
|
||||
var floatProd float64 = 1.0
|
||||
var intProd int64 = 1
|
||||
var fractProd *fraction
|
||||
var fractProd *FractionType
|
||||
var v any
|
||||
|
||||
level++
|
||||
@@ -136,9 +136,9 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
|
||||
if mulAsFloat {
|
||||
floatProd *= numAsFloat(v)
|
||||
} else if mulAsFract {
|
||||
var item *fraction
|
||||
var item *FractionType
|
||||
var ok bool
|
||||
if item, ok = v.(*fraction); !ok {
|
||||
if item, ok = v.(*FractionType); !ok {
|
||||
iv, _ := v.(int64)
|
||||
item = newFraction(iv, 1)
|
||||
}
|
||||
@@ -167,10 +167,15 @@ func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
}
|
||||
|
||||
func ImportMathFuncs(ctx ExprContext) {
|
||||
ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
|
||||
ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
|
||||
ctx.RegisterFunc("add", &golangFunctor{f: addFunc}, TypeNumber, []ExprFuncParam{
|
||||
NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, int64(0)),
|
||||
})
|
||||
|
||||
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()")
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// builtin-os-file.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type osHandle interface {
|
||||
getFile() *os.File
|
||||
}
|
||||
|
||||
type osWriter struct {
|
||||
fh *os.File
|
||||
writer *bufio.Writer
|
||||
}
|
||||
|
||||
func (h *osWriter) TypeName() string {
|
||||
return "osWriter"
|
||||
}
|
||||
|
||||
func (h *osWriter) String() string {
|
||||
return "writer"
|
||||
}
|
||||
|
||||
func (h *osWriter) getFile() *os.File {
|
||||
return h.fh
|
||||
}
|
||||
|
||||
type osReader struct {
|
||||
fh *os.File
|
||||
reader *bufio.Reader
|
||||
}
|
||||
|
||||
func (h *osReader) TypeName() string {
|
||||
return "osReader"
|
||||
}
|
||||
|
||||
func (h *osReader) String() string {
|
||||
return "reader"
|
||||
}
|
||||
|
||||
func (h *osReader) getFile() *os.File {
|
||||
return h.fh
|
||||
}
|
||||
|
||||
func errMissingFilePath(funcName string) error {
|
||||
return fmt.Errorf("%s(): missing or invalid file path", funcName)
|
||||
}
|
||||
|
||||
func errInvalidFileHandle(funcName string, v any) error {
|
||||
if v != nil {
|
||||
return fmt.Errorf("%s(): invalid file handle %v [%T]", funcName, v, v)
|
||||
} else {
|
||||
return fmt.Errorf("%s(): invalid file handle", funcName)
|
||||
}
|
||||
}
|
||||
|
||||
func createFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
if filePath, ok := args[0].(string); ok && len(filePath) > 0 {
|
||||
var fh *os.File
|
||||
if fh, err = os.Create(filePath); err == nil {
|
||||
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
|
||||
}
|
||||
} else {
|
||||
err = errMissingFilePath("createFile")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func openFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
if filePath, ok := args[0].(string); ok && len(filePath) > 0 {
|
||||
var fh *os.File
|
||||
if fh, err = os.Open(filePath); err == nil {
|
||||
result = &osReader{fh: fh, reader: bufio.NewReader(fh)}
|
||||
}
|
||||
} else {
|
||||
err = errMissingFilePath("openFile")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
if filePath, ok := args[0].(string); ok && len(filePath) > 0 {
|
||||
var fh *os.File
|
||||
if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil {
|
||||
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
|
||||
}
|
||||
} else {
|
||||
err = errMissingFilePath("appendFile")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var handle osHandle
|
||||
var invalidFileHandle any
|
||||
var ok bool
|
||||
|
||||
if handle, ok = args[0].(osHandle); !ok {
|
||||
invalidFileHandle = args[0]
|
||||
}
|
||||
|
||||
if handle != nil {
|
||||
if fh := handle.getFile(); fh != nil {
|
||||
if w, ok := handle.(*osWriter); ok {
|
||||
err = w.writer.Flush()
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = fh.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == nil && (handle == nil || invalidFileHandle != nil) {
|
||||
err = errInvalidFileHandle("closeFileFunc", handle)
|
||||
}
|
||||
result = err == nil
|
||||
return
|
||||
}
|
||||
|
||||
func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var handle osHandle
|
||||
var invalidFileHandle any
|
||||
var ok bool
|
||||
|
||||
if handle, ok = args[0].(osHandle); !ok {
|
||||
invalidFileHandle = args[0]
|
||||
}
|
||||
|
||||
if handle != nil {
|
||||
if w, ok := handle.(*osWriter); ok {
|
||||
result, err = fmt.Fprint(w.writer, args[1:]...)
|
||||
} else {
|
||||
invalidFileHandle = handle
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil && (handle == nil || invalidFileHandle != nil) {
|
||||
err = errInvalidFileHandle("writeFileFunc", invalidFileHandle)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func readFileFunc(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 limit byte = '\n'
|
||||
var v string
|
||||
if s, ok := args[1].(string); ok && len(s) > 0 {
|
||||
limit = s[0]
|
||||
}
|
||||
|
||||
if v, err = r.reader.ReadString(limit); err == nil {
|
||||
if len(v) > 0 && v[len(v)-1] == limit {
|
||||
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)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ImportOsFuncs(ctx ExprContext) {
|
||||
ctx.RegisterFunc("fileOpen", NewGolangFunctor(openFileFunc), TypeHandle, []ExprFuncParam{
|
||||
NewFuncParam(ParamFilepath),
|
||||
})
|
||||
ctx.RegisterFunc("fileAppend", NewGolangFunctor(appendFileFunc), TypeHandle, []ExprFuncParam{
|
||||
NewFuncParam(ParamFilepath),
|
||||
})
|
||||
ctx.RegisterFunc("fileCreate", NewGolangFunctor(createFileFunc), TypeHandle, []ExprFuncParam{
|
||||
NewFuncParam(ParamFilepath),
|
||||
})
|
||||
ctx.RegisterFunc("fileWrite", NewGolangFunctor(writeFileFunc), TypeInt, []ExprFuncParam{
|
||||
NewFuncParam(TypeHandle),
|
||||
NewFuncParamFlagDef(TypeItem, PfDefault|PfRepeat, ""),
|
||||
})
|
||||
ctx.RegisterFunc("fileRead", NewGolangFunctor(readFileFunc), TypeString, []ExprFuncParam{
|
||||
NewFuncParam(TypeHandle),
|
||||
NewFuncParamFlagDef("limitCh", PfDefault, "\n"),
|
||||
})
|
||||
ctx.RegisterFunc("fileClose", NewGolangFunctor(closeFileFunc), TypeBoolean, []ExprFuncParam{
|
||||
NewFuncParam(TypeHandle),
|
||||
})
|
||||
}
|
||||
|
||||
func init() {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -33,9 +33,9 @@ func doJoinStr(funcName string, sep string, it Iterator) (result any, err error)
|
||||
}
|
||||
|
||||
func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
// if len(args) < 1 {
|
||||
// return nil, errMissingRequiredParameter(name, paramSeparator)
|
||||
// }
|
||||
// if len(args) < 1 {
|
||||
// return nil, errMissingRequiredParameter(name, paramSeparator)
|
||||
// }
|
||||
if sep, ok := args[0].(string); ok {
|
||||
if len(args) == 1 {
|
||||
result = ""
|
||||
@@ -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
|
||||
}
|
||||
@@ -62,25 +62,22 @@ func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error
|
||||
var source string
|
||||
var ok bool
|
||||
|
||||
// if len(args) < 1 {
|
||||
// return nil, errMissingRequiredParameter(name, paramSource)
|
||||
// }
|
||||
if source, ok = args[0].(string); !ok {
|
||||
return nil, errWrongParamType(name, paramSource, typeString, args[0])
|
||||
return nil, ErrWrongParamType(name, ParamSource, TypeString, args[0])
|
||||
}
|
||||
if len(args) > 1 {
|
||||
if start, err = toInt(args[1], name+"()"); err != nil {
|
||||
|
||||
if start, err = ToInt(args[1], name+"()"); err != nil {
|
||||
return
|
||||
}
|
||||
if len(args) > 2 {
|
||||
if count, err = toInt(args[2], name+"()"); err != nil {
|
||||
|
||||
if count, err = ToInt(args[2], name+"()"); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if start < 0 {
|
||||
start = len(source) + start
|
||||
}
|
||||
}
|
||||
|
||||
if count < 0 {
|
||||
count = len(source) - start
|
||||
}
|
||||
@@ -93,11 +90,8 @@ func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err erro
|
||||
var source string
|
||||
var ok bool
|
||||
|
||||
// if len(args) < 1 {
|
||||
// return nil, errMissingRequiredParameter(name, paramSource)
|
||||
// }
|
||||
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
|
||||
@@ -108,11 +102,9 @@ func startsWithStrFunc(ctx ExprContext, name string, args []any) (result any, er
|
||||
var ok bool
|
||||
|
||||
result = false
|
||||
// if len(args) < 1 {
|
||||
// return result, errMissingRequiredParameter(name, paramSource)
|
||||
// }
|
||||
|
||||
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 {
|
||||
@@ -133,11 +125,9 @@ func endsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err
|
||||
var ok bool
|
||||
|
||||
result = false
|
||||
// if len(args) < 1 {
|
||||
// return result, errMissingRequiredParameter(name, paramSource)
|
||||
// }
|
||||
|
||||
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 {
|
||||
@@ -159,24 +149,20 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
|
||||
var parts []string
|
||||
var ok bool
|
||||
|
||||
// if len(args) < 1 {
|
||||
// return result, errMissingRequiredParameter(name, paramSource)
|
||||
// }
|
||||
if source, ok = args[0].(string); !ok {
|
||||
return result, errWrongParamType(name, paramSource, typeString, args[0])
|
||||
return result, ErrWrongParamType(name, ParamSource, TypeString, args[0])
|
||||
}
|
||||
if len(args) >= 2 {
|
||||
|
||||
if sep, ok = args[1].(string); !ok {
|
||||
return nil, fmt.Errorf("separator param must be string, got %T (%v)", args[1], args[1])
|
||||
}
|
||||
if len(args) >= 3 {
|
||||
|
||||
if count64, ok := args[2].(int64); ok { // TODO replace type assertion with toInt()
|
||||
count = int(count64)
|
||||
} else {
|
||||
return nil, fmt.Errorf("part count must be integer, got %T (%v)", args[2], args[2])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
parts = strings.SplitN(source, sep, count)
|
||||
} else if count < 0 {
|
||||
@@ -196,16 +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", &simpleFunctor{f: joinStrFunc}, 1, -1)
|
||||
ctx.RegisterFunc("subStr", &simpleFunctor{f: subStrFunc}, 1, -1)
|
||||
ctx.RegisterFunc("splitStr", &simpleFunctor{f: splitStrFunc}, 2, -1)
|
||||
ctx.RegisterFunc("trimStr", &simpleFunctor{f: trimStrFunc}, 1, -1)
|
||||
ctx.RegisterFunc("startsWithStr", &simpleFunctor{f: startsWithStrFunc}, 2, -1)
|
||||
ctx.RegisterFunc("endsWithStr", &simpleFunctor{f: endsWithStrFunc}, 2, -1)
|
||||
ctx.RegisterFunc("strJoin", NewGolangFunctor(joinStrFunc), TypeString, []ExprFuncParam{
|
||||
NewFuncParam(ParamSeparator),
|
||||
NewFuncParamFlag(ParamItem, PfRepeat),
|
||||
})
|
||||
|
||||
ctx.RegisterFunc("strSub", NewGolangFunctor(subStrFunc), TypeString, []ExprFuncParam{
|
||||
NewFuncParam(ParamSource),
|
||||
NewFuncParamFlagDef(ParamStart, PfDefault, int64(0)),
|
||||
NewFuncParamFlagDef(ParamCount, PfDefault, int64(-1)),
|
||||
})
|
||||
|
||||
ctx.RegisterFunc("strSplit", NewGolangFunctor(splitStrFunc), "list of "+TypeString, []ExprFuncParam{
|
||||
NewFuncParam(ParamSource),
|
||||
NewFuncParamFlagDef(ParamSeparator, PfDefault, ""),
|
||||
NewFuncParamFlagDef(ParamCount, PfDefault, int64(-1)),
|
||||
})
|
||||
|
||||
ctx.RegisterFunc("strTrim", NewGolangFunctor(trimStrFunc), TypeString, []ExprFuncParam{
|
||||
NewFuncParam(ParamSource),
|
||||
})
|
||||
|
||||
ctx.RegisterFunc("strStartsWith", NewGolangFunctor(startsWithStrFunc), TypeBoolean, []ExprFuncParam{
|
||||
NewFuncParam(ParamSource),
|
||||
NewFuncParam(ParamPrefix),
|
||||
NewFuncParamFlag("other "+ParamPrefix, 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)
|
||||
}
|
||||
}
|
||||
+21
-21
@@ -8,48 +8,48 @@ import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func errTooFewParams(minArgs, maxArgs, argCount int) (err error) {
|
||||
func ErrTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error) {
|
||||
if maxArgs < 0 {
|
||||
err = fmt.Errorf("too few params -- expected %d or more, got %d", minArgs, argCount)
|
||||
err = fmt.Errorf("%s(): too few params -- expected %d or more, got %d", funcName, minArgs, argCount)
|
||||
} else {
|
||||
err = fmt.Errorf("too few params -- expected %d, got %d", minArgs, argCount)
|
||||
err = fmt.Errorf("%s(): too few params -- expected %d, got %d", funcName, minArgs, argCount)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func errTooMuchParams(maxArgs, argCount int) (err error) {
|
||||
err = fmt.Errorf("too much params -- expected %d, got %d", maxArgs, argCount)
|
||||
func ErrTooMuchParams(funcName string, maxArgs, argCount int) (err error) {
|
||||
err = fmt.Errorf("%s(): too much params -- expected %d, got %d", funcName, maxArgs, argCount)
|
||||
return
|
||||
}
|
||||
|
||||
// --- General errors
|
||||
|
||||
func errCantConvert(funcName string, value any, kind string) error {
|
||||
return fmt.Errorf("%s() can't convert %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 errDivisionByZero(funcName string) error {
|
||||
return fmt.Errorf("%s() division by zero", funcName)
|
||||
func ErrFuncDivisionByZero(funcName string) error {
|
||||
return fmt.Errorf("%s(): division by zero", funcName)
|
||||
}
|
||||
|
||||
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 %T (%v) for parameter %q", funcName, paramValue, paramValue, paramName)
|
||||
func ErrInvalidParameterValue(funcName, paramName string, paramValue any) error {
|
||||
return fmt.Errorf("%s(): invalid value %s (%v) for parameter %q", funcName, TypeName(paramValue), paramValue, paramName)
|
||||
}
|
||||
|
||||
func errWrongParamType(funcName, paramName, paramType string, paramValue any) error {
|
||||
return fmt.Errorf("%s() the %q parameter must be a %s, got a %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)
|
||||
}
|
||||
|
||||
+13
-3
@@ -5,7 +5,17 @@
|
||||
package expr
|
||||
|
||||
const (
|
||||
paramParts = "parts"
|
||||
paramSeparator = "separator"
|
||||
paramSource = "source"
|
||||
ParamCount = "count"
|
||||
ParamItem = "item"
|
||||
ParamParts = "parts"
|
||||
ParamSeparator = "separator"
|
||||
ParamSource = "source"
|
||||
ParamSuffix = "suffix"
|
||||
ParamPrefix = "prefix"
|
||||
ParamStart = "start"
|
||||
ParamEnd = "end"
|
||||
ParamValue = "value"
|
||||
ParamEllipsis = "..."
|
||||
ParamFilepath = "filepath"
|
||||
ParamDirpath = "dirpath"
|
||||
)
|
||||
|
||||
+12
-6
@@ -5,10 +5,16 @@
|
||||
package expr
|
||||
|
||||
const (
|
||||
typeBoolean = "boolean"
|
||||
typeFloat = "decimal"
|
||||
typeFraction = "fraction"
|
||||
typeInt = "integer"
|
||||
typeNumber = "number"
|
||||
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"
|
||||
)
|
||||
|
||||
+2
-2
@@ -22,11 +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.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] == '@' }) {
|
||||
|
||||
+20
-10
@@ -4,33 +4,41 @@
|
||||
// context.go
|
||||
package expr
|
||||
|
||||
// ---- Function template
|
||||
type FuncTemplate func(ctx ExprContext, name string, args []any) (result any, err error)
|
||||
|
||||
// ---- Functor interface
|
||||
type Functor interface {
|
||||
Invoke(ctx ExprContext, name string, args []any) (result any, err error)
|
||||
SetFunc(info ExprFunc)
|
||||
GetFunc() ExprFunc
|
||||
GetParams() []ExprFuncParam
|
||||
}
|
||||
|
||||
type simpleFunctor struct {
|
||||
f FuncTemplate
|
||||
}
|
||||
|
||||
func (functor *simpleFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
return functor.f(ctx, name, args)
|
||||
// ---- Function Param Info
|
||||
type ExprFuncParam interface {
|
||||
Name() string
|
||||
Type() string
|
||||
IsDefault() bool
|
||||
IsOptional() bool
|
||||
IsRepeat() bool
|
||||
DefaultValue() any
|
||||
}
|
||||
|
||||
// ---- Function Info
|
||||
type ExprFunc interface {
|
||||
Formatter
|
||||
Name() string
|
||||
MinArgs() int
|
||||
MaxArgs() int
|
||||
Functor() Functor
|
||||
Params() []ExprFuncParam
|
||||
ReturnType() string
|
||||
}
|
||||
|
||||
// ----Expression Context
|
||||
type ExprContext interface {
|
||||
Clone() ExprContext
|
||||
Merge(ctx ExprContext)
|
||||
SetParent(ctx ExprContext)
|
||||
GetParent() (ctx ExprContext)
|
||||
GetVar(varName string) (value any, exists bool)
|
||||
SetVar(varName string, value any)
|
||||
UnsafeSetVar(varName string, value any)
|
||||
@@ -38,5 +46,7 @@ type ExprContext interface {
|
||||
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)
|
||||
// RegisterFunc(name string, f Functor, minArgs, maxArgs int)
|
||||
RegisterFuncInfo(info ExprFunc)
|
||||
RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) error
|
||||
}
|
||||
|
||||
+9
-38
@@ -4,13 +4,13 @@
|
||||
// control.go
|
||||
package expr
|
||||
|
||||
import "strings"
|
||||
|
||||
// Preset control variables
|
||||
const (
|
||||
ControlPreset = "_preset"
|
||||
ControlLastResult = "last"
|
||||
ControlBoolShortcut = "_bool_shortcut"
|
||||
ControlImportPath = "_import_path"
|
||||
ControlSearchPath = "_search_path"
|
||||
ControlParentContext = "_parent_context"
|
||||
)
|
||||
|
||||
// Other control variables
|
||||
@@ -20,43 +20,14 @@ const (
|
||||
|
||||
// Initial values
|
||||
const (
|
||||
init_import_path = "~/.local/lib/go-pkg/expr:/usr/local/lib/go-pkg/expr:/usr/lib/go-pkg/expr"
|
||||
init_search_path = "~/.local/lib/go-pkg/expr:/usr/local/lib/go-pkg/expr:/usr/lib/go-pkg/expr"
|
||||
)
|
||||
|
||||
func initDefaultVars(ctx ExprContext) {
|
||||
if _, exists := ctx.GetVar(ControlPreset); exists {
|
||||
return
|
||||
}
|
||||
ctx.SetVar(ControlPreset, true)
|
||||
ctx.SetVar(ControlBoolShortcut, true)
|
||||
ctx.SetVar(ControlImportPath, init_import_path)
|
||||
}
|
||||
|
||||
func enable(ctx ExprContext, name string) {
|
||||
if strings.HasPrefix(name, "_") {
|
||||
ctx.SetVar(name, true)
|
||||
} else {
|
||||
ctx.SetVar("_"+name, true)
|
||||
}
|
||||
}
|
||||
|
||||
func disable(ctx ExprContext, name string) {
|
||||
if strings.HasPrefix(name, "_") {
|
||||
ctx.SetVar(name, false)
|
||||
} else {
|
||||
ctx.SetVar("_"+name, false)
|
||||
}
|
||||
}
|
||||
|
||||
func isEnabled(ctx ExprContext, name string) (status bool) {
|
||||
if v, exists := ctx.GetVar(name); exists {
|
||||
if b, ok := v.(bool); ok {
|
||||
status = b
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getControlString(ctx ExprContext, name string) (s string, exists bool) {
|
||||
var v any
|
||||
if v, exists = ctx.GetVar(name); exists {
|
||||
s, exists = v.(string)
|
||||
}
|
||||
return
|
||||
ctx.SetVar(ControlSearchPath, init_search_path)
|
||||
}
|
||||
|
||||
+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
|
||||
}
|
||||
+9
-4
@@ -22,7 +22,7 @@ Expressions calculator
|
||||
|
||||
toc::[]
|
||||
|
||||
#TODO: Work in progress (last update on 2024/05/20, 06:58 a.m.)#
|
||||
#TODO: Work in progress (last update on 2024/06/02, 08:18 a.m.)#
|
||||
|
||||
== Expr
|
||||
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
|
||||
@@ -295,7 +295,7 @@ _item_ = _string-expr_ "**.**" _integer-expr_
|
||||
=== Booleans
|
||||
Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relational and boolean expressions result in boolean values.
|
||||
|
||||
.Relational operators
|
||||
.Relational operators^(*)^
|
||||
[cols="^1,^2,6,4"]
|
||||
|===
|
||||
| Symbol | Operation | Description | Examples
|
||||
@@ -314,6 +314,8 @@ Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relationa
|
||||
[blue]`"b" \<= "b"` -> _true_
|
||||
|===
|
||||
|
||||
^(*)^ See also the [blue]`in` operator in the _list_ and _dictionary_ sections.
|
||||
|
||||
|
||||
.Boolean operators
|
||||
[cols="^2,^2,5,4"]
|
||||
@@ -575,7 +577,7 @@ 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_
|
||||
| [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_
|
||||
@@ -613,7 +615,10 @@ The table below shows all supported operators by decreasing priorities.
|
||||
|===
|
||||
|
||||
== Functions
|
||||
Functions in _Expr_ are very similar to functions in many programming languages.
|
||||
Functions in _Expr_ are very similar to functions available in many programming languages. Actually, _Expr_ supports two types of function, _expr-functions_ and _go-functions_.
|
||||
|
||||
* _expr-functions_ are defined using _Expr_'s syntax. They can be passed as arguments to other functions and can be returned from functions. Moreover, they bind themselves to the defining context, thus becoming closures.
|
||||
* _go-functions_ are regular Golang functions callable from _Expr_ expressions. They are defined in Golang source files called _modules_ and compiled within the _Expr_ package. To make Golang functions available in _Expr_ contextes, it is required to _import_ the module in which they are defined.
|
||||
|
||||
In _Expr_ functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator [blue]`@` it is possibile to export local definition to the calling context.
|
||||
|
||||
|
||||
+18
-5
@@ -585,7 +585,7 @@ pre.rouge .ss {
|
||||
<div class="sectionbody">
|
||||
<!-- toc disabled -->
|
||||
<div class="paragraph">
|
||||
<p><mark>TODO: Work in progress (last update on 2024/05/17, 15:47 p.m.)</mark></p>
|
||||
<p><mark>TODO: Work in progress (last update on 2024/06/02, 08:18 a.m.)</mark></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1041,7 +1041,7 @@ pre.rouge .ss {
|
||||
<p>Boolean data type has two values only: <em class="blue">true</em> and <em class="blue">false</em>. Relational and boolean expressions result in boolean values.</p>
|
||||
</div>
|
||||
<table class="tableblock frame-all grid-all stretch">
|
||||
<caption class="title">Table 4. Relational operators</caption>
|
||||
<caption class="title">Table 4. Relational operators<sup>(*)</sup></caption>
|
||||
<colgroup>
|
||||
<col style="width: 7.6923%;">
|
||||
<col style="width: 15.3846%;">
|
||||
@@ -1101,6 +1101,9 @@ pre.rouge .ss {
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="paragraph">
|
||||
<p><sup>(*)</sup> See also the <code class="blue">in</code> operator in the <em>list</em> and <em>dictionary</em> sections.</p>
|
||||
</div>
|
||||
<table class="tableblock frame-all grid-all stretch">
|
||||
<caption class="title">Table 5. Boolean operators</caption>
|
||||
<colgroup>
|
||||
@@ -1606,7 +1609,7 @@ These operators have a high priority, in particular higher than the operator <co
|
||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><code class="blue">.</code></p></td>
|
||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Infix</em></p></td>
|
||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>Dict item</em></p></td>
|
||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>dict</em> <code>""</code> <em>any</em> → <em>any</em></p></td>
|
||||
<td class="tableblock halign-center valign-top"><p class="tableblock"><em>dict</em> <code>"."</code> <em>any</em> → <em>any</em></p></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tableblock halign-center valign-top" rowspan="2"><p class="tableblock"><strong>INC</strong></p></td>
|
||||
@@ -1831,7 +1834,17 @@ These operators have a high priority, in particular higher than the operator <co
|
||||
<h2 id="_functions"><a class="anchor" href="#_functions"></a><a class="link" href="#_functions">6. Functions</a></h2>
|
||||
<div class="sectionbody">
|
||||
<div class="paragraph">
|
||||
<p>Functions in <em>Expr</em> are very similar to functions in many programming languages.</p>
|
||||
<p>Functions in <em>Expr</em> are very similar to functions available in many programming languages. Actually, <em>Expr</em> supports two types of function, <em>expr-functions</em> and <em>go-functions</em>.</p>
|
||||
</div>
|
||||
<div class="ulist">
|
||||
<ul>
|
||||
<li>
|
||||
<p><em>expr-functions</em> are defined using <em>Expr</em>'s syntax. They can be passed as arguments to other functions and can be returned from functions. Moreover, they bind themselves to the defining context, thus becoming closures.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><em>go-functions</em> are regular Golang functions callable from <em>Expr</em> expressions. They are defined in Golang source files called <em>modules</em> and compiled within the <em>Expr</em> package. To make Golang functions available in <em>Expr</em> contextes, it is required to <em>import</em> the module in which they are defined.</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="paragraph">
|
||||
<p>In <em>Expr</em> functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator <code class="blue">@</code> it is possibile to export local definition to the calling context.</p>
|
||||
@@ -1871,7 +1884,7 @@ These operators have a high priority, in particular higher than the operator <co
|
||||
</div>
|
||||
<div id="footer">
|
||||
<div id="footer-text">
|
||||
Last updated 2024-05-20 06:58:09 +0200
|
||||
Last updated 2024-06-03 06:26:03 +0200
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// expr_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExpr(t *testing.T) {
|
||||
type inputType struct {
|
||||
source string
|
||||
wantResult any
|
||||
wantErr error
|
||||
}
|
||||
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`0?{}`, nil, nil},
|
||||
/* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
|
||||
/* 3 */ {`f=openFile("test-file.txt"); line=readFile(f); closeFile(f); line`, "uno", nil},
|
||||
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
|
||||
/* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil},
|
||||
/* 6 */ {`
|
||||
ds={
|
||||
"init":func(end){@end=end; @current=0 but true},
|
||||
"current":func(){current},
|
||||
"next":func(){
|
||||
((next=current+1) <= end) ? [true] {@current=next but current} :: {nil}
|
||||
}
|
||||
};
|
||||
it=$(ds,3);
|
||||
it++;
|
||||
it++
|
||||
`, int64(1), nil},
|
||||
}
|
||||
|
||||
succeeded := 0
|
||||
failed := 0
|
||||
|
||||
for i, input := range inputs {
|
||||
var expr Expr
|
||||
var gotResult any
|
||||
var gotErr error
|
||||
|
||||
ctx := NewSimpleFuncStore()
|
||||
// ImportMathFuncs(ctx)
|
||||
// ImportImportFunc(ctx)
|
||||
ImportOsFuncs(ctx)
|
||||
parser := NewParser(ctx)
|
||||
|
||||
logTest(t, i+1, "Expr", input.source, input.wantResult, input.wantErr)
|
||||
|
||||
r := strings.NewReader(input.source)
|
||||
scanner := NewScanner(r, DefaultTranslations())
|
||||
|
||||
good := true
|
||||
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
|
||||
gotResult, gotErr = expr.Eval(ctx)
|
||||
}
|
||||
|
||||
if gotResult != input.wantResult {
|
||||
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
||||
good = false
|
||||
}
|
||||
|
||||
if gotErr != input.wantErr {
|
||||
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
|
||||
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
|
||||
good = false
|
||||
}
|
||||
}
|
||||
|
||||
if good {
|
||||
succeeded++
|
||||
} else {
|
||||
failed++
|
||||
}
|
||||
}
|
||||
t.Logf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed)
|
||||
}
|
||||
+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"
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// fraction-type.go
|
||||
package expr
|
||||
|
||||
//https://www.youmath.it/lezioni/algebra-elementare/lezioni-di-algebra-e-aritmetica-per-scuole-medie/553-dalle-frazioni-a-numeri-decimali.html
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type FractionType struct {
|
||||
num, den int64
|
||||
}
|
||||
|
||||
func newFraction(num, den int64) *FractionType {
|
||||
num, den = simplifyIntegers(num, den)
|
||||
return &FractionType{num, den}
|
||||
}
|
||||
|
||||
func float64ToFraction(f float64) (fract *FractionType, err error) {
|
||||
var sign string
|
||||
intPart, decPart := math.Modf(f)
|
||||
if decPart < 0.0 {
|
||||
sign = "-"
|
||||
intPart = -intPart
|
||||
decPart = -decPart
|
||||
}
|
||||
dec := fmt.Sprintf("%.12f", decPart)
|
||||
s := fmt.Sprintf("%s%.f%s", sign, intPart, dec[1:])
|
||||
return makeGeneratingFraction(s)
|
||||
}
|
||||
|
||||
// Based on https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/math/big/rat.go;l=39
|
||||
func makeGeneratingFraction(s string) (f *FractionType, err error) {
|
||||
var num, den int64
|
||||
var sign int64 = 1
|
||||
var parts []string
|
||||
if len(s) == 0 {
|
||||
goto exit
|
||||
}
|
||||
if s[0] == '-' {
|
||||
sign = int64(-1)
|
||||
s = s[1:]
|
||||
} else if s[0] == '+' {
|
||||
s = s[1:]
|
||||
}
|
||||
if strings.HasSuffix(s, "()") {
|
||||
s = s[0 : len(s)-2]
|
||||
}
|
||||
parts = strings.SplitN(s, ".", 2)
|
||||
if num, err = strconv.ParseInt(parts[0], 10, 64); err != nil {
|
||||
return
|
||||
}
|
||||
if len(parts) == 1 {
|
||||
f = newFraction(sign*num, 1)
|
||||
} else if len(parts) == 2 {
|
||||
subParts := strings.SplitN(parts[1], "(", 2)
|
||||
if len(subParts) == 1 {
|
||||
den = 1
|
||||
dec := parts[1]
|
||||
lsd := len(dec)
|
||||
for i := lsd - 1; i >= 0 && dec[i] == '0'; i-- {
|
||||
lsd--
|
||||
}
|
||||
for _, c := range dec[0:lsd] {
|
||||
if c < '0' || c > '9' {
|
||||
return nil, ErrExpectedGot("fract", "digit", c)
|
||||
}
|
||||
num = num*10 + int64(c-'0')
|
||||
den = den * 10
|
||||
}
|
||||
f = newFraction(sign*num, den)
|
||||
} else if len(subParts) == 2 {
|
||||
sub := num
|
||||
mul := int64(1)
|
||||
for _, c := range subParts[0] {
|
||||
if c < '0' || c > '9' {
|
||||
return nil, ErrExpectedGot("fract", "digit", c)
|
||||
}
|
||||
num = num*10 + int64(c-'0')
|
||||
sub = sub*10 + int64(c-'0')
|
||||
mul *= 10
|
||||
}
|
||||
if len(subParts) == 2 {
|
||||
if s[len(s)-1] != ')' {
|
||||
goto exit
|
||||
}
|
||||
p := subParts[1][0 : len(subParts[1])-1]
|
||||
for _, c := range p {
|
||||
if c < '0' || c > '9' {
|
||||
return nil, ErrExpectedGot("fract", "digit", c)
|
||||
}
|
||||
num = num*10 + int64(c-'0')
|
||||
den = den*10 + 9
|
||||
}
|
||||
den *= mul
|
||||
}
|
||||
num -= sub
|
||||
f = newFraction(sign*num, den)
|
||||
}
|
||||
}
|
||||
exit:
|
||||
if f == nil {
|
||||
err = errors.New("bad syntax")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *FractionType) toFloat() float64 {
|
||||
return float64(f.num) / float64(f.den)
|
||||
}
|
||||
|
||||
func (f *FractionType) String() string {
|
||||
return f.ToString(0)
|
||||
}
|
||||
|
||||
func (f *FractionType) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
if opt&MultiLine == 0 {
|
||||
sb.WriteString(fmt.Sprintf("%d|%d", f.num, f.den))
|
||||
} else {
|
||||
var s, num string
|
||||
if f.num < 0 && opt&TTY == 0 {
|
||||
num = strconv.FormatInt(-f.num, 10)
|
||||
s = "-"
|
||||
} else {
|
||||
num = strconv.FormatInt(f.num, 10)
|
||||
}
|
||||
den := strconv.FormatInt(f.den, 10)
|
||||
size := max(len(num), len(den))
|
||||
if opt&TTY != 0 {
|
||||
sb.WriteString(fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, s+num)))
|
||||
} else {
|
||||
if len(s) > 0 {
|
||||
sb.WriteString(" ")
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, num)))
|
||||
sb.WriteByte('\n')
|
||||
if len(s) > 0 {
|
||||
sb.WriteString(s)
|
||||
sb.WriteByte(' ')
|
||||
}
|
||||
sb.WriteString(strings.Repeat("-", size))
|
||||
sb.WriteByte('\n')
|
||||
if len(s) > 0 {
|
||||
sb.WriteString(" ")
|
||||
}
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den)))
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (f *FractionType) TypeName() string {
|
||||
return "fraction"
|
||||
}
|
||||
|
||||
// -------- fraction utility functions
|
||||
|
||||
// greatest common divider
|
||||
func gcd(a, b int64) (g int64) {
|
||||
if a < 0 {
|
||||
a = -a
|
||||
}
|
||||
if b < 0 {
|
||||
b = -b
|
||||
}
|
||||
if a < b {
|
||||
a, b = b, a
|
||||
}
|
||||
r := a % b
|
||||
for r > 0 {
|
||||
a, b = b, r
|
||||
r = a % b
|
||||
}
|
||||
g = b
|
||||
return
|
||||
}
|
||||
|
||||
// lower common multiple
|
||||
func lcm(a, b int64) (l int64) {
|
||||
g := gcd(a, b)
|
||||
l = a * b / g
|
||||
return
|
||||
}
|
||||
|
||||
// Sum two fractions
|
||||
func sumFract(f1, f2 *FractionType) (sum *FractionType) {
|
||||
m := lcm(f1.den, f2.den)
|
||||
sum = newFraction(f1.num*(m/f1.den)+f2.num*(m/f2.den), m)
|
||||
return
|
||||
}
|
||||
|
||||
// Multiply two fractions
|
||||
func mulFract(f1, f2 *FractionType) (prod *FractionType) {
|
||||
prod = newFraction(f1.num*f2.num, f1.den*f2.den)
|
||||
return
|
||||
}
|
||||
|
||||
func anyToFract(v any) (f *FractionType, err error) {
|
||||
var ok bool
|
||||
if f, ok = v.(*FractionType); !ok {
|
||||
if n, ok := v.(int64); ok {
|
||||
f = intToFraction(n)
|
||||
}
|
||||
}
|
||||
if f == nil {
|
||||
err = ErrExpectedGot("fract", TypeFraction, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func anyPairToFract(v1, v2 any) (f1, f2 *FractionType, err error) {
|
||||
if f1, err = anyToFract(v1); err != nil {
|
||||
return
|
||||
}
|
||||
if f2, err = anyToFract(v2); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func sumAnyFract(af1, af2 any) (sum any, err error) {
|
||||
var f1, f2 *FractionType
|
||||
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||
return
|
||||
}
|
||||
f := sumFract(f1, f2)
|
||||
if f.num == 0 {
|
||||
sum = 0
|
||||
} else {
|
||||
sum = simplifyFraction(f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Returns
|
||||
//
|
||||
// <0 if af1 < af2
|
||||
// =0 if af1 == af2
|
||||
// >0 if af1 > af2
|
||||
// err if af1 or af2 is not convertible to fraction
|
||||
func cmpAnyFract(af1, af2 any) (result int, err error) {
|
||||
var f1, f2 *FractionType
|
||||
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||
return
|
||||
}
|
||||
result = cmpFract(f1, f2)
|
||||
return
|
||||
}
|
||||
|
||||
// Returns
|
||||
//
|
||||
// <0 if af1 < af2
|
||||
// =0 if af1 == af2
|
||||
// >0 if af1 > af2
|
||||
func cmpFract(f1, f2 *FractionType) (result int) {
|
||||
f2.num = -f2.num
|
||||
f := sumFract(f1, f2)
|
||||
if f.num < 0 {
|
||||
result = -1
|
||||
} else if f.num > 0 {
|
||||
result = 1
|
||||
} else {
|
||||
result = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func subAnyFract(af1, af2 any) (sum any, err error) {
|
||||
var f1, f2 *FractionType
|
||||
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||
return
|
||||
}
|
||||
f2.num = -f2.num
|
||||
f := sumFract(f1, f2)
|
||||
if f.num == 0 {
|
||||
sum = 0
|
||||
} else {
|
||||
sum = simplifyFraction(f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func mulAnyFract(af1, af2 any) (prod any, err error) {
|
||||
var f1, f2 *FractionType
|
||||
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||
return
|
||||
}
|
||||
if f1.num == 0 || f2.num == 0 {
|
||||
prod = 0
|
||||
} else {
|
||||
f := &FractionType{f1.num * f2.num, f1.den * f2.den}
|
||||
prod = simplifyFraction(f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func divAnyFract(af1, af2 any) (quot any, err error) {
|
||||
var f1, f2 *FractionType
|
||||
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||
return
|
||||
}
|
||||
if f2.num == 0 {
|
||||
err = errors.New("division by zero")
|
||||
return
|
||||
return
|
||||
}
|
||||
if f1.num == 0 || f2.den == 0 {
|
||||
quot = 0
|
||||
} else {
|
||||
f := &FractionType{f1.num * f2.den, f1.den * f2.num}
|
||||
quot = simplifyFraction(f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func simplifyFraction(f *FractionType) (v any) {
|
||||
f.num, f.den = simplifyIntegers(f.num, f.den)
|
||||
if f.den == 1 {
|
||||
v = f.num
|
||||
} else {
|
||||
v = &FractionType{f.num, f.den}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func simplifyIntegers(num, den int64) (a, b int64) {
|
||||
if num == 0 {
|
||||
return 0, 1
|
||||
}
|
||||
if den == 0 {
|
||||
panic("fraction with denominator == 0")
|
||||
}
|
||||
if den < 0 {
|
||||
den = -den
|
||||
num = -num
|
||||
}
|
||||
g := gcd(num, den)
|
||||
a = num / g
|
||||
b = den / g
|
||||
return
|
||||
}
|
||||
|
||||
func intToFraction(n int64) *FractionType {
|
||||
return &FractionType{n, 1}
|
||||
}
|
||||
|
||||
func isFraction(v any) (ok bool) {
|
||||
_, ok = v.(*FractionType)
|
||||
return ok
|
||||
}
|
||||
-146
@@ -1,146 +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", &simpleFunctor{f: importFunc}, 1, -1)
|
||||
ctx.RegisterFunc("importAll", &simpleFunctor{f: importAllFunc}, 1, -1)
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerImport("import", ImportImportFuncs, "Functions import() and include()")
|
||||
}
|
||||
-171
@@ -1,171 +0,0 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// func-os.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type osHandle interface {
|
||||
getFile() *os.File
|
||||
}
|
||||
|
||||
type osWriter struct {
|
||||
fh *os.File
|
||||
writer *bufio.Writer
|
||||
}
|
||||
|
||||
func (h *osWriter) getFile() *os.File {
|
||||
return h.fh
|
||||
}
|
||||
|
||||
type osReader struct {
|
||||
fh *os.File
|
||||
reader *bufio.Reader
|
||||
}
|
||||
|
||||
func (h *osReader) getFile() *os.File {
|
||||
return h.fh
|
||||
}
|
||||
|
||||
func createFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var filePath string
|
||||
if len(args) > 0 {
|
||||
filePath, _ = args[0].(string)
|
||||
}
|
||||
|
||||
if len(filePath) > 0 {
|
||||
var fh *os.File
|
||||
if fh, err = os.Create(filePath); err == nil {
|
||||
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("%s(): missing the file path", name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func openFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var filePath string
|
||||
if len(args) > 0 {
|
||||
filePath, _ = args[0].(string)
|
||||
}
|
||||
|
||||
if len(filePath) > 0 {
|
||||
var fh *os.File
|
||||
if fh, err = os.Open(filePath); err == nil {
|
||||
result = &osReader{fh: fh, reader: bufio.NewReader(fh)}
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("%s(): missing the file path", name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var filePath string
|
||||
if len(args) > 0 {
|
||||
filePath, _ = args[0].(string)
|
||||
}
|
||||
|
||||
if len(filePath) > 0 {
|
||||
var fh *os.File
|
||||
if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil {
|
||||
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("%s(): missing the file path", name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var handle osHandle
|
||||
|
||||
if len(args) > 0 {
|
||||
handle, _ = args[0].(osHandle)
|
||||
}
|
||||
|
||||
if handle != nil {
|
||||
if fh := handle.getFile(); fh != nil {
|
||||
if w, ok := handle.(*osWriter); ok {
|
||||
err = w.writer.Flush()
|
||||
}
|
||||
if err == nil {
|
||||
err = fh.Close()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("%s(): invalid file handle", name)
|
||||
}
|
||||
result = err == nil
|
||||
return
|
||||
}
|
||||
|
||||
func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var handle osHandle
|
||||
|
||||
if len(args) > 0 {
|
||||
handle, _ = args[0].(osHandle)
|
||||
}
|
||||
|
||||
if handle != nil {
|
||||
if fh := handle.getFile(); fh != nil {
|
||||
if w, ok := handle.(*osWriter); ok {
|
||||
result, err = fmt.Fprint(w.writer, args[1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var handle osHandle
|
||||
result = nil
|
||||
if len(args) > 0 {
|
||||
handle, _ = args[0].(osHandle)
|
||||
}
|
||||
|
||||
if handle != nil {
|
||||
if fh := handle.getFile(); fh != nil {
|
||||
if r, ok := handle.(*osReader); ok {
|
||||
var limit byte = '\n'
|
||||
var v string
|
||||
if len(args) > 1 {
|
||||
if s, ok := args[1].(string); ok && len(s) > 0 {
|
||||
limit = s[0]
|
||||
}
|
||||
}
|
||||
if v, err = r.reader.ReadString(limit); err == nil {
|
||||
if len(v) > 0 && v[len(v)-1] == limit {
|
||||
result = v[0 : len(v)-1]
|
||||
} else {
|
||||
result = v
|
||||
}
|
||||
}
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ImportOsFuncs(ctx ExprContext) {
|
||||
ctx.RegisterFunc("openFile", &simpleFunctor{f: openFileFunc}, 1, 1)
|
||||
ctx.RegisterFunc("appendFile", &simpleFunctor{f: appendFileFunc}, 1, 1)
|
||||
ctx.RegisterFunc("createFile", &simpleFunctor{f: createFileFunc}, 1, 1)
|
||||
ctx.RegisterFunc("writeFile", &simpleFunctor{f: writeFileFunc}, 1, -1)
|
||||
ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2)
|
||||
ctx.RegisterFunc("closeFile", &simpleFunctor{f: closeFileFunc}, 1, 1)
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerImport("os.file", ImportOsFuncs, "Operating system file functions")
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// funcs_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFuncs(t *testing.T) {
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`isNil(nil)`, true, nil},
|
||||
/* 2 */ {`v=nil; isNil(v)`, true, nil},
|
||||
/* 3 */ {`v=5; isNil(v)`, false, nil},
|
||||
/* 4 */ {`int(true)`, int64(1), nil},
|
||||
/* 5 */ {`int(false)`, int64(0), nil},
|
||||
/* 6 */ {`int(3.1)`, int64(3), nil},
|
||||
/* 7 */ {`int(3.9)`, int64(3), nil},
|
||||
/* 8 */ {`int("432")`, int64(432), nil},
|
||||
/* 9 */ {`int("1.5")`, nil, errors.New(`strconv.Atoi: parsing "1.5": invalid syntax`)},
|
||||
/* 10 */ {`int("432", 4)`, nil, errors.New(`too much params -- expected 1, got 2`)},
|
||||
/* 11 */ {`int(nil)`, nil, errors.New(`int() can't convert <nil> to int`)},
|
||||
/* 12 */ {`two=func(){2}; two()`, int64(2), nil},
|
||||
/* 13 */ {`double=func(x) {2*x}; (double(3))`, int64(6), nil},
|
||||
/* 14 */ {`double=func(x){2*x}; double(3)`, int64(6), nil},
|
||||
/* 15 */ {`double=func(x){2*x}; a=5; double(3+a) + 1`, int64(17), nil},
|
||||
/* 16 */ {`double=func(x){2*x}; a=5; two=func() {2}; (double(3+a) + 1) * two()`, int64(34), nil},
|
||||
/* 17 */ {`builtin "import"; import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
||||
/* 18 */ {`builtin "import"; import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
||||
/* 19 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
|
||||
/* 20 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
|
||||
/* 21 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
|
||||
/* 22 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable or function "x"`)},
|
||||
/* 23 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
||||
/* 24 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
||||
/* 25 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
|
||||
/* 26 */ {`builtin "import"; importAll("./test-funcs.expr"); six()`, int64(6), nil},
|
||||
/* 27 */ {`builtin "import"; import("./sample-export-all.expr"); six()`, int64(6), nil},
|
||||
/* 28 */ {`builtin "string"; joinStr("-", "one", "two", "three")`, "one-two-three", nil},
|
||||
/* 29 */ {`builtin "string"; joinStr("-", ["one", "two", "three"])`, "one-two-three", nil},
|
||||
/* 30 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr("-", ls)`, "one-two-three", nil},
|
||||
/* 31 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr(1, ls)`, nil, errors.New(`joinStr() the "separator" parameter must be a string, got a int64 (1)`)},
|
||||
/* 32 */ {`builtin "string"; ls= ["one", 2, "three"]; joinStr("-", ls)`, nil, errors.New(`joinStr() expected string, got int64 (2)`)},
|
||||
/* 33 */ {`builtin "string"; "<"+trimStr(" bye bye ")+">"`, "<bye bye>", nil},
|
||||
/* 34 */ {`builtin "string"; subStr("0123456789", 1,2)`, "12", nil},
|
||||
/* 35 */ {`builtin "string"; subStr("0123456789", -3,2)`, "78", nil},
|
||||
/* 36 */ {`builtin "string"; subStr("0123456789", -3)`, "789", nil},
|
||||
/* 37 */ {`builtin "string"; subStr("0123456789")`, "0123456789", nil},
|
||||
/* 38 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "012")`, true, nil},
|
||||
/* 39 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "0125")`, false, nil},
|
||||
/* 40 */ {`builtin "string"; startsWithStr("0123456789")`, nil, errors.New(`too few params -- expected 2 or more, got 1`)},
|
||||
/* 41 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "789")`, true, nil},
|
||||
/* 42 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "0125")`, false, nil},
|
||||
/* 43 */ {`builtin "string"; endsWithStr("0123456789")`, nil, errors.New(`too few params -- expected 2 or more, got 1`)},
|
||||
/* 44 */ {`builtin "string"; splitStr("one-two-three", "-", )`, newListA("one", "two", "three"), nil},
|
||||
/* 45 */ {`isInt(2+1)`, true, nil},
|
||||
/* 46 */ {`isInt(3.1)`, false, nil},
|
||||
/* 47 */ {`isFloat(3.1)`, true, nil},
|
||||
/* 48 */ {`isString("3.1")`, true, nil},
|
||||
/* 49 */ {`isString("3" + 1)`, true, nil},
|
||||
/* 50 */ {`isList(["3", 1])`, true, nil},
|
||||
/* 51 */ {`isDict({"a":"3", "b":1})`, true, nil},
|
||||
/* 52 */ {`isFract(1|3)`, true, nil},
|
||||
/* 53 */ {`isFract(3|1)`, false, nil},
|
||||
/* 54 */ {`isRational(3|1)`, true, nil},
|
||||
/* 55 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil},
|
||||
/* 56 */ {`fract("2.2(3)")`, newFraction(67, 30), nil},
|
||||
/* 57 */ {`fract("1.21(3)")`, newFraction(91, 75), nil},
|
||||
/* 58 */ {`fract(1.21(3))`, newFraction(91, 75), nil},
|
||||
/* 59 */ {`fract(1.21)`, newFraction(121, 100), nil},
|
||||
/* 60 */ {`dec(2)`, float64(2), nil},
|
||||
/* 61 */ {`dec(2.0)`, float64(2), nil},
|
||||
/* 62 */ {`dec("2.0")`, float64(2), nil},
|
||||
/* 63 */ {`dec(true)`, float64(1), nil},
|
||||
/* 64 */ {`dec(true")`, nil, errors.New("[1:11] missing string termination \"")},
|
||||
/* 65 */ {`builtin "string"; joinStr("-", [1, "two", "three"])`, nil, errors.New(`joinStr() expected string, got int64 (1)`)},
|
||||
/* 65 */ {`dec()`, nil, errors.New(`too few params -- expected 1, got 0`)},
|
||||
/* 66 */ {`dec(1,2,3)`, nil, errors.New(`too much params -- expected 1, got 3`)},
|
||||
/* 67 */ {`builtin "string"; joinStr()`, nil, errors.New(`too few params -- expected 1 or more, got 0`)},
|
||||
// /* 64 */ {`string(true)`, "true", nil},
|
||||
}
|
||||
|
||||
t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
//parserTest(t, "Func", inputs[54:55])
|
||||
parserTest(t, "Func", inputs)
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// function.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ---- Function template
|
||||
type FuncTemplate func(ctx ExprContext, name string, args []any) (result any, err error)
|
||||
|
||||
// ---- Common functor definition
|
||||
type baseFunctor struct {
|
||||
info ExprFunc
|
||||
}
|
||||
|
||||
func (functor *baseFunctor) ToString(opt FmtOpt) (s string) {
|
||||
if functor.info != nil {
|
||||
s = functor.info.ToString(opt)
|
||||
} else {
|
||||
s = "func() {}"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (functor *baseFunctor) GetParams() (params []ExprFuncParam) {
|
||||
if functor.info != nil {
|
||||
return functor.info.Params()
|
||||
} else {
|
||||
return []ExprFuncParam{}
|
||||
}
|
||||
}
|
||||
|
||||
func (functor *baseFunctor) SetFunc(info ExprFunc) {
|
||||
functor.info = info
|
||||
}
|
||||
|
||||
func (functor *baseFunctor) GetFunc() ExprFunc {
|
||||
return functor.info
|
||||
}
|
||||
|
||||
// ---- Function Parameters
|
||||
type paramFlags uint16
|
||||
|
||||
const (
|
||||
PfDefault paramFlags = 1 << iota
|
||||
PfOptional
|
||||
PfRepeat
|
||||
)
|
||||
|
||||
type funcParamInfo struct {
|
||||
name string
|
||||
flags paramFlags
|
||||
defaultValue any
|
||||
}
|
||||
|
||||
func NewFuncParam(name string) ExprFuncParam {
|
||||
return &funcParamInfo{name: name}
|
||||
}
|
||||
|
||||
func NewFuncParamFlag(name string, flags paramFlags) ExprFuncParam {
|
||||
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 TypeAny
|
||||
}
|
||||
|
||||
func (param *funcParamInfo) IsDefault() bool {
|
||||
return (param.flags & PfDefault) != 0
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// --- Functions
|
||||
type funcInfo struct {
|
||||
name string
|
||||
minArgs int
|
||||
maxArgs int
|
||||
functor Functor
|
||||
params []ExprFuncParam
|
||||
returnType string
|
||||
}
|
||||
|
||||
func newFuncInfo(name string, functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
|
||||
var minArgs = 0
|
||||
var maxArgs = 0
|
||||
if params != nil {
|
||||
for _, p := range params {
|
||||
if maxArgs == -1 {
|
||||
return nil, fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name())
|
||||
}
|
||||
if p.IsDefault() || p.IsOptional() {
|
||||
maxArgs++
|
||||
} else if maxArgs == minArgs {
|
||||
minArgs++
|
||||
maxArgs++
|
||||
} else {
|
||||
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,
|
||||
}
|
||||
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 (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
|
||||
if len(info.Name()) == 0 {
|
||||
sb.WriteString("func")
|
||||
} else {
|
||||
sb.WriteString(info.Name())
|
||||
}
|
||||
sb.WriteByte('(')
|
||||
if info.params != nil {
|
||||
for i, p := range info.params {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(p.Name())
|
||||
|
||||
if p.IsDefault() {
|
||||
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)
|
||||
}
|
||||
sb.WriteString(" {}")
|
||||
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
|
||||
}
|
||||
+96
-7
@@ -4,13 +4,16 @@
|
||||
// global-context.go
|
||||
package expr
|
||||
|
||||
import "path/filepath"
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var globalCtx *SimpleFuncStore
|
||||
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,15 +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 {
|
||||
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 = NewSimpleFuncStore()
|
||||
globalCtx = NewSimpleStore()
|
||||
initDefaultVars(globalCtx)
|
||||
ImportBuiltinsFuncs(globalCtx)
|
||||
}
|
||||
|
||||
+9
-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
|
||||
}
|
||||
@@ -34,12 +34,15 @@ func EvalStringA(source string, args ...Arg) (result any, err error) {
|
||||
}
|
||||
|
||||
func EvalStringV(source string, args []Arg) (result any, err error) {
|
||||
ctx := NewSimpleFuncStore()
|
||||
ctx := NewSimpleStore()
|
||||
for _, arg := range args {
|
||||
if isFunc(arg.Value) {
|
||||
if f, ok := arg.Value.(FuncTemplate); ok {
|
||||
functor := &simpleFunctor{f: f}
|
||||
ctx.RegisterFunc(arg.Name, functor, 0, -1)
|
||||
functor := NewGolangFunctor(f)
|
||||
// ctx.RegisterFunc(arg.Name, functor, 0, -1)
|
||||
ctx.RegisterFunc(arg.Name, functor, TypeAny, []ExprFuncParam{
|
||||
NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, 0),
|
||||
})
|
||||
} else {
|
||||
err = fmt.Errorf("invalid function specification: %q", arg.Name)
|
||||
}
|
||||
@@ -65,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
|
||||
}
|
||||
+13
-4
@@ -26,21 +26,21 @@ func NewListIterator(list *ListType, args []any) (it *ListIterator) {
|
||||
}
|
||||
it = &ListIterator{a: list, count: 0, index: -1, start: 0, stop: listLen - 1, step: 1}
|
||||
if argc >= 1 {
|
||||
if i, err := toInt(args[0], "start index"); err == nil {
|
||||
if i, err := ToInt(args[0], "start index"); err == nil {
|
||||
if i < 0 {
|
||||
i = listLen + i
|
||||
}
|
||||
it.start = i
|
||||
}
|
||||
if argc >= 2 {
|
||||
if i, err := toInt(args[1], "stop index"); err == nil {
|
||||
if i, err := ToInt(args[1], "stop index"); err == nil {
|
||||
if i < 0 {
|
||||
i = listLen + i
|
||||
}
|
||||
it.stop = i
|
||||
}
|
||||
if argc >= 3 {
|
||||
if i, err := toInt(args[2], "step"); err == nil {
|
||||
if i, err := ToInt(args[2], "step"); err == nil {
|
||||
if i < 0 {
|
||||
i = -i
|
||||
}
|
||||
@@ -106,11 +106,20 @@ 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 {
|
||||
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 {
|
||||
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 {
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// iterator_test.go
|
||||
package expr
|
||||
|
||||
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},
|
||||
/* 11 */ {`it=$(1,2,3); it++`, int64(1), nil},
|
||||
}
|
||||
// inputs1 := []inputType{
|
||||
// /* 1 */ {`0?{}`, nil, nil},
|
||||
// }
|
||||
parserTest(t, "Iterator", inputs)
|
||||
}
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// list-type.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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 {
|
||||
ls[i] = item
|
||||
}
|
||||
list = &ls
|
||||
}
|
||||
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 {
|
||||
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 flags&MultiLine != 0 {
|
||||
sb.WriteString(",\n")
|
||||
sb.WriteString(nest)
|
||||
} else {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
}
|
||||
if s, ok := item.(string); ok {
|
||||
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 flags&MultiLine != 0 {
|
||||
sb.WriteByte('\n')
|
||||
sb.WriteString(strings.Repeat(" ", indent))
|
||||
}
|
||||
}
|
||||
sb.WriteByte(']')
|
||||
s = sb.String()
|
||||
if flags&Truncate != 0 && len(s) > TruncateSize {
|
||||
s = TruncateString(s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ls *ListType) String() string {
|
||||
return ls.ToString(0)
|
||||
}
|
||||
|
||||
func (ls *ListType) TypeName() string {
|
||||
return "list"
|
||||
}
|
||||
|
||||
func (list *ListType) indexDeepCmp(target any) (index int) {
|
||||
index = -1
|
||||
for i, item := range *list {
|
||||
if reflect.DeepEqual(item, target) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ls *ListType) contains(t *ListType) (answer bool) {
|
||||
if len(*ls) >= len(*t) {
|
||||
answer = true
|
||||
for _, item := range *t {
|
||||
if answer = ls.indexDeepSameCmp(item) >= 0; !answer {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (list *ListType) indexDeepSameCmp(target any) (index int) {
|
||||
var eq bool
|
||||
var err error
|
||||
index = -1
|
||||
for i, item := range *list {
|
||||
if eq, err = deepSame(item, target, sameContent); err != nil {
|
||||
break
|
||||
} else if eq {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func sameContent(a, b any) (same bool, err error) {
|
||||
la, _ := a.(*ListType)
|
||||
lb, _ := b.(*ListType)
|
||||
if len(*la) == len(*lb) {
|
||||
same = true
|
||||
for _, item := range *la {
|
||||
if pos := lb.indexDeepSameCmp(item); pos < 0 {
|
||||
same = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func deepSame(a, b any, deepCmp deepFuncTemplate) (eq bool, err error) {
|
||||
if isNumOrFract(a) && isNumOrFract(b) {
|
||||
if IsNumber(a) && IsNumber(b) {
|
||||
if IsInteger(a) && IsInteger(b) {
|
||||
li, _ := a.(int64)
|
||||
ri, _ := b.(int64)
|
||||
eq = li == ri
|
||||
} else {
|
||||
eq = numAsFloat(a) == numAsFloat(b)
|
||||
}
|
||||
} else {
|
||||
var cmp int
|
||||
if cmp, err = cmpAnyFract(a, b); err == nil {
|
||||
eq = cmp == 0
|
||||
}
|
||||
}
|
||||
} else if deepCmp != nil && IsList(a) && IsList(b) {
|
||||
eq, err = deepCmp(a, b)
|
||||
} else {
|
||||
eq = reflect.DeepEqual(a, b)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
-122
@@ -1,122 +0,0 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// list_test.go
|
||||
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},
|
||||
/* 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},
|
||||
/* 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},
|
||||
/* 17 */ {`list=["one","two","three"]; list.10`, nil, errors.New(`[1:36] index 10 out of bounds`)},
|
||||
/* 18 */ {`["a", "b", "c"]`, newListA("a", "b", "c"), nil},
|
||||
/* 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},
|
||||
|
||||
// /* 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},
|
||||
}
|
||||
|
||||
succeeded := 0
|
||||
failed := 0
|
||||
|
||||
// inputs1 := []inputType{
|
||||
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
|
||||
// }
|
||||
|
||||
for i, input := range inputs {
|
||||
var expr *ast
|
||||
var gotResult any
|
||||
var gotErr error
|
||||
|
||||
ctx := NewSimpleFuncStore()
|
||||
// ctx.SetVar("var1", int64(123))
|
||||
// ctx.SetVar("var2", "abc")
|
||||
ImportMathFuncs(ctx)
|
||||
parser := NewParser(ctx)
|
||||
|
||||
logTest(t, i+1, "List", input.source, input.wantResult, input.wantErr)
|
||||
|
||||
r := strings.NewReader(input.source)
|
||||
scanner := NewScanner(r, DefaultTranslations())
|
||||
|
||||
good := true
|
||||
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
|
||||
gotResult, gotErr = expr.Eval(ctx)
|
||||
}
|
||||
|
||||
if (gotResult == nil && input.wantResult != nil) || (gotResult != nil && input.wantResult == nil) {
|
||||
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
||||
good = false
|
||||
}
|
||||
|
||||
if gotList, okGot := gotResult.([]any); okGot {
|
||||
if wantList, okWant := input.wantResult.([]any); okWant {
|
||||
if (gotList == nil && wantList != nil) || (gotList != nil && wantList == nil) {
|
||||
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
||||
good = false
|
||||
} else {
|
||||
equal := len(gotList) == len(wantList)
|
||||
if equal {
|
||||
for i, gotItem := range gotList {
|
||||
wantItem := wantList[i]
|
||||
equal = gotItem == wantItem
|
||||
if !equal {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !equal {
|
||||
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
||||
good = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if gotErr != input.wantErr {
|
||||
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
|
||||
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
|
||||
good = false
|
||||
}
|
||||
}
|
||||
|
||||
if good {
|
||||
succeeded++
|
||||
} else {
|
||||
failed++
|
||||
}
|
||||
}
|
||||
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||
}
|
||||
@@ -1,73 +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 ImportInContext(ctx ExprContext, name string) (exists bool) {
|
||||
// var mod *module
|
||||
// if mod, exists = moduleRegister[name]; exists {
|
||||
// mod.importFunc(ctx)
|
||||
// mod.imported = true
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
// func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) {
|
||||
// var matched bool
|
||||
// for name, mod := range moduleRegister {
|
||||
// if matched, err = filepath.Match(pattern, name); err == nil {
|
||||
// if matched {
|
||||
// count++
|
||||
// mod.importFunc(ctx)
|
||||
// mod.imported = true
|
||||
// }
|
||||
// } else {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
func IterateModules(op func(name, description string, imported bool) bool) {
|
||||
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 {
|
||||
|
||||
+30
-37
@@ -22,16 +22,25 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
|
||||
}
|
||||
|
||||
// -------- eval func call
|
||||
func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) {
|
||||
func checkFunctionCall(ctx ExprContext, name string, varParams *[]any) (err error) {
|
||||
if info, exists, owner := GetFuncInfo(ctx, name); exists {
|
||||
if info.MinArgs() > len(params) {
|
||||
err = errTooFewParams(info.MinArgs(), info.MaxArgs(), len(params))
|
||||
passedCount := len(*varParams)
|
||||
if info.MinArgs() > passedCount {
|
||||
err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
|
||||
}
|
||||
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(params) {
|
||||
err = errTooMuchParams(info.MaxArgs(), len(params))
|
||||
for i, p := range info.Params() {
|
||||
if i >= passedCount {
|
||||
if !p.IsDefault() {
|
||||
break
|
||||
}
|
||||
*varParams = append(*varParams, p.DefaultValue())
|
||||
}
|
||||
}
|
||||
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(*varParams) {
|
||||
err = ErrTooMuchParams(name, info.MaxArgs(), len(*varParams))
|
||||
}
|
||||
if err == nil && owner != ctx {
|
||||
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
|
||||
ctx.RegisterFuncInfo(info)
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("unknown function %s()", name)
|
||||
@@ -41,9 +50,9 @@ func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) {
|
||||
|
||||
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
|
||||
ctx := cloneContext(parentCtx)
|
||||
ctx.SetParent(parentCtx)
|
||||
name, _ := self.tk.Value.(string)
|
||||
// fmt.Printf("Call %s(), context: %p\n", name, ctx)
|
||||
params := make([]any, len(self.children))
|
||||
params := make([]any, len(self.children), len(self.children)+5)
|
||||
for i, tree := range self.children {
|
||||
var param any
|
||||
if param, err = tree.compute(ctx); err != nil {
|
||||
@@ -51,8 +60,9 @@ func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
|
||||
}
|
||||
params[i] = param
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
if err = checkFunctionCall(ctx, name, params); err == nil {
|
||||
if err = checkFunctionCall(ctx, name, ¶ms); err == nil {
|
||||
if v, err = ctx.Call(name, params); err == nil {
|
||||
exportObjects(parentCtx, ctx)
|
||||
}
|
||||
@@ -74,40 +84,23 @@ func newFuncDefTerm(tk *Token, args []*term) *term {
|
||||
}
|
||||
|
||||
// -------- eval func def
|
||||
// TODO
|
||||
type funcDefFunctor struct {
|
||||
params []string
|
||||
expr Expr
|
||||
}
|
||||
|
||||
func (functor *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
for i, p := range functor.params {
|
||||
if i < len(args) {
|
||||
arg := args[i]
|
||||
if functor, ok := arg.(Functor); ok {
|
||||
ctx.RegisterFunc(p, functor, 0, -1)
|
||||
} else {
|
||||
ctx.UnsafeSetVar(p, arg)
|
||||
}
|
||||
} else {
|
||||
ctx.UnsafeSetVar(p, nil)
|
||||
}
|
||||
}
|
||||
result, err = functor.expr.eval(ctx, false)
|
||||
return
|
||||
}
|
||||
|
||||
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))
|
||||
paramList := make([]ExprFuncParam, 0, len(self.children))
|
||||
for _, param := range self.children {
|
||||
paramList = append(paramList, param.source())
|
||||
var defValue any
|
||||
flags := paramFlags(0)
|
||||
if len(param.children) > 0 {
|
||||
flags |= PfDefault
|
||||
if defValue, err = param.children[0].compute(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
v = &funcDefFunctor{
|
||||
params: paramList,
|
||||
expr: expr,
|
||||
}
|
||||
info := NewFuncParamFlagDef(param.source(), flags, defValue)
|
||||
paramList = append(paramList, info)
|
||||
}
|
||||
v = newExprFunctor(expr, paramList, ctx)
|
||||
} else {
|
||||
err = errors.New("invalid function definition: the body specification must be an expression")
|
||||
}
|
||||
|
||||
+2
-18
@@ -74,7 +74,8 @@ func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map
|
||||
ds = make(map[string]Functor)
|
||||
for keyAny, item := range *dictAny {
|
||||
if key, ok := keyAny.(string); ok {
|
||||
if functor, ok := item.(*funcDefFunctor); ok {
|
||||
//if functor, ok := item.(*funcDefFunctor); ok {
|
||||
if functor, ok := item.(Functor); ok {
|
||||
ds[key] = functor
|
||||
if index := slices.Index(requiredFields, key); index >= 0 {
|
||||
foundFields |= 1 << index
|
||||
@@ -147,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 {
|
||||
|
||||
+3
-80
@@ -4,91 +4,14 @@
|
||||
// operand-list.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ListType []any
|
||||
|
||||
func newListA(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 {
|
||||
ls[i] = item
|
||||
}
|
||||
list = &ls
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ls *ListType) ToString(opt FmtOpt) (s string) {
|
||||
var sb strings.Builder
|
||||
sb.WriteByte('[')
|
||||
if len(*ls) > 0 {
|
||||
if opt&MultiLine != 0 {
|
||||
sb.WriteString("\n ")
|
||||
}
|
||||
for i, item := range []any(*ls) {
|
||||
if i > 0 {
|
||||
if opt&MultiLine != 0 {
|
||||
sb.WriteString(",\n ")
|
||||
} else {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
}
|
||||
if s, ok := item.(string); ok {
|
||||
sb.WriteByte('"')
|
||||
sb.WriteString(s)
|
||||
sb.WriteByte('"')
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", item))
|
||||
}
|
||||
}
|
||||
if opt&MultiLine != 0 {
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
sb.WriteByte(']')
|
||||
s = sb.String()
|
||||
if opt&Truncate != 0 && len(s) > TruncateSize {
|
||||
s = TruncateString(s)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ls *ListType) String() string {
|
||||
return ls.ToString(0)
|
||||
}
|
||||
|
||||
func (ls *ListType) TypeName() string {
|
||||
return "list"
|
||||
}
|
||||
|
||||
func (list *ListType) indexDeepCmp(target any) (index int) {
|
||||
index = -1
|
||||
for i, item := range *list {
|
||||
if reflect.DeepEqual(item, target) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// -------- list term
|
||||
func newListTermA(args ...*term) *term {
|
||||
return newListTerm(args)
|
||||
return newListTerm(0, 0, args)
|
||||
}
|
||||
|
||||
func newListTerm(args []*term) *term {
|
||||
func newListTerm(row, col int, args []*term) *term {
|
||||
return &term{
|
||||
tk: *NewValueToken(0, 0, SymList, "[]", args),
|
||||
tk: *NewValueToken(row, col, SymList, "[]", args),
|
||||
parent: nil,
|
||||
children: nil,
|
||||
position: posLeaf,
|
||||
|
||||
@@ -10,8 +10,6 @@ import "fmt"
|
||||
func newVarTerm(tk *Token) *term {
|
||||
t := &term{
|
||||
tk: *tk,
|
||||
// class: classVar,
|
||||
// kind: kindUnknown,
|
||||
parent: nil,
|
||||
children: nil,
|
||||
position: posLeaf,
|
||||
|
||||
+59
-14
@@ -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,21 +76,21 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
|
||||
|
||||
if v, err = rightChild.compute(ctx); err == nil {
|
||||
if functor, ok := v.(Functor); ok {
|
||||
var minArgs, maxArgs int = 0, -1
|
||||
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, func(p ExprFuncParam) ExprFuncParam { return p })
|
||||
|
||||
funcName := rightChild.source()
|
||||
if info, exists := ctx.GetFuncInfo(funcName); exists {
|
||||
minArgs = info.MinArgs()
|
||||
maxArgs = info.MaxArgs()
|
||||
} else if funcDef, ok := functor.(*funcDefFunctor); ok {
|
||||
l := len(funcDef.params)
|
||||
minArgs = l
|
||||
maxArgs = l
|
||||
}
|
||||
ctx.RegisterFunc(leftTerm.source(), functor, minArgs, maxArgs)
|
||||
ctx.RegisterFunc(leftTerm.source(), functor, TypeAny, paramSpecs)
|
||||
} else {
|
||||
ctx.UnsafeSetVar(leftTerm.source(), v)
|
||||
err = self.Errorf("unknown function %s()", rightChild.source())
|
||||
}
|
||||
} else {
|
||||
err = assignValue(ctx, leftTerm, v)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
v = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
+11
-11
@@ -25,7 +25,7 @@ func evalNot(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if b, ok := toBool(rightValue); ok {
|
||||
if b, ok := ToBool(rightValue); ok {
|
||||
v = !b
|
||||
} else {
|
||||
err = self.errIncompatibleType(rightValue)
|
||||
@@ -48,7 +48,7 @@ func newAndTerm(tk *Token) (inst *term) {
|
||||
}
|
||||
|
||||
func evalAnd(ctx ExprContext, self *term) (v any, err error) {
|
||||
if isEnabled(ctx, ControlBoolShortcut) {
|
||||
if CtrlIsEnabled(ctx, ControlBoolShortcut) {
|
||||
v, err = evalAndWithShortcut(ctx, self)
|
||||
} else {
|
||||
v, err = evalAndWithoutShortcut(ctx, self)
|
||||
@@ -65,8 +65,8 @@ func evalAndWithoutShortcut(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
leftBool, lok = toBool(leftValue)
|
||||
rightBool, rok = toBool(rightValue)
|
||||
leftBool, lok = ToBool(leftValue)
|
||||
rightBool, rok = ToBool(rightValue)
|
||||
|
||||
if lok && rok {
|
||||
v = leftBool && rightBool
|
||||
@@ -87,13 +87,13 @@ func evalAndWithShortcut(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if leftBool, lok := toBool(leftValue); !lok {
|
||||
if leftBool, lok := ToBool(leftValue); !lok {
|
||||
err = fmt.Errorf("got %T as left operand type of 'and' operator, it must be bool", leftBool)
|
||||
return
|
||||
} else if !leftBool {
|
||||
v = false
|
||||
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
||||
if rightBool, rok := toBool(rightValue); rok {
|
||||
if rightBool, rok := ToBool(rightValue); rok {
|
||||
v = rightBool
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
@@ -117,7 +117,7 @@ func newOrTerm(tk *Token) (inst *term) {
|
||||
}
|
||||
|
||||
func evalOr(ctx ExprContext, self *term) (v any, err error) {
|
||||
if isEnabled(ctx, ControlBoolShortcut) {
|
||||
if CtrlIsEnabled(ctx, ControlBoolShortcut) {
|
||||
v, err = evalOrWithShortcut(ctx, self)
|
||||
} else {
|
||||
v, err = evalOrWithoutShortcut(ctx, self)
|
||||
@@ -134,8 +134,8 @@ func evalOrWithoutShortcut(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
leftBool, lok = toBool(leftValue)
|
||||
rightBool, rok = toBool(rightValue)
|
||||
leftBool, lok = ToBool(leftValue)
|
||||
rightBool, rok = ToBool(rightValue)
|
||||
|
||||
if lok && rok {
|
||||
v = leftBool || rightBool
|
||||
@@ -156,13 +156,13 @@ func evalOrWithShortcut(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if leftBool, lok := toBool(leftValue); !lok {
|
||||
if leftBool, lok := ToBool(leftValue); !lok {
|
||||
err = fmt.Errorf("got %T as left operand type of 'or' operator, it must be bool", leftBool)
|
||||
return
|
||||
} else if leftBool {
|
||||
v = true
|
||||
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
||||
if rightBool, rok := toBool(rightValue); rok {
|
||||
if rightBool, rok := ToBool(rightValue); rok {
|
||||
v = rightBool
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
|
||||
+2
-2
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,10 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
||||
v = leftValue
|
||||
} 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, 0, -1)
|
||||
ctx.RegisterFunc(leftTerm.source(), functor, TypeAny, []ExprFuncParam{
|
||||
NewFuncParamFlag(ParamValue, PfDefault|PfRepeat),
|
||||
})
|
||||
} else {
|
||||
v = rightValue
|
||||
ctx.UnsafeSetVar(leftTerm.source(), rightValue)
|
||||
|
||||
+8
-5
@@ -20,21 +20,24 @@ func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
|
||||
var childValue any
|
||||
|
||||
var sourceCtx ExprContext
|
||||
if len(self.children) == 0 {
|
||||
if self.children == nil || len(self.children) == 0 {
|
||||
sourceCtx = ctx
|
||||
} else if self.children[0].symbol() == SymVariable && self.children[0].source() == "global" {
|
||||
sourceCtx = globalCtx
|
||||
} else if childValue, err = self.evalPrefix(ctx); err == nil {
|
||||
if dc, ok := childValue.(*dataCursor); ok {
|
||||
sourceCtx = dc.ctx
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
if sourceCtx != nil {
|
||||
if formatter, ok := ctx.(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
|
||||
}
|
||||
|
||||
|
||||
+5
-40
@@ -4,8 +4,6 @@
|
||||
// operator-dot.go
|
||||
package expr
|
||||
|
||||
import "fmt"
|
||||
|
||||
// -------- dot term
|
||||
func newDotTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
@@ -17,24 +15,6 @@ func newDotTerm(tk *Token) (inst *term) {
|
||||
}
|
||||
}
|
||||
|
||||
func verifyIndex(ctx ExprContext, indexTerm *term, maxValue int) (index int, err error) {
|
||||
var v int
|
||||
var indexValue any
|
||||
if indexValue, err = indexTerm.compute(ctx); err == nil {
|
||||
if v, err = indexTerm.toInt(indexValue, "index expression value must be integer"); err == nil {
|
||||
if v < 0 && v >= -maxValue {
|
||||
v = maxValue + v
|
||||
}
|
||||
if v >= 0 && v < maxValue {
|
||||
index = v
|
||||
} else {
|
||||
err = indexTerm.Errorf("index %d out of bounds", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func evalDot(ctx ExprContext, self *term) (v any, err error) {
|
||||
var leftValue, rightValue any
|
||||
|
||||
@@ -48,27 +28,8 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
|
||||
indexTerm := self.children[1]
|
||||
|
||||
switch unboxedValue := leftValue.(type) {
|
||||
case *ListType:
|
||||
var index int
|
||||
array := ([]any)(*unboxedValue)
|
||||
if index, err = verifyIndex(ctx, indexTerm, len(array)); err == nil {
|
||||
v = array[index]
|
||||
}
|
||||
case string:
|
||||
var index int
|
||||
if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil {
|
||||
v = string(unboxedValue[index])
|
||||
}
|
||||
case *DictType:
|
||||
var ok bool
|
||||
var indexValue any
|
||||
if indexValue, err = indexTerm.compute(ctx); err == nil {
|
||||
if v, ok = (*unboxedValue)[indexValue]; !ok {
|
||||
err = fmt.Errorf("key %v does not belong to the dictionary", rightValue)
|
||||
}
|
||||
}
|
||||
case ExtIterator:
|
||||
if indexTerm.symbol() == SymVariable {
|
||||
if indexTerm.symbol() == SymVariable /*|| indexTerm.symbol() == SymString */ {
|
||||
opName := indexTerm.source()
|
||||
if unboxedValue.HasOperation(opName) {
|
||||
v, err = unboxedValue.CallOperation(opName, []any{})
|
||||
@@ -76,10 +37,14 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
|
||||
err = indexTerm.Errorf("this iterator do not support the %q command", opName)
|
||||
v = false
|
||||
}
|
||||
} else {
|
||||
err = indexTerm.tk.ErrorExpectedGot("identifier")
|
||||
}
|
||||
default:
|
||||
if rightValue, err = self.children[1].compute(ctx); err == nil {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+1
-355
@@ -9,205 +9,8 @@ package expr
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type fraction struct {
|
||||
num, den int64
|
||||
}
|
||||
|
||||
func newFraction(num, den int64) *fraction {
|
||||
/* if den < 0 {
|
||||
den = -den
|
||||
num = -num
|
||||
}*/
|
||||
num, den = simplifyIntegers(num, den)
|
||||
return &fraction{num, den}
|
||||
}
|
||||
|
||||
func float64ToFraction(f float64) (fract *fraction, err error) {
|
||||
var sign string
|
||||
intPart, decPart := math.Modf(f)
|
||||
if decPart < 0.0 {
|
||||
sign = "-"
|
||||
intPart = -intPart
|
||||
decPart = -decPart
|
||||
}
|
||||
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 *fraction, err error) {
|
||||
var num, den int64
|
||||
var sign int64 = 1
|
||||
var parts []string
|
||||
if len(s) == 0 {
|
||||
goto exit
|
||||
}
|
||||
if s[0] == '-' {
|
||||
sign = int64(-1)
|
||||
s = s[1:]
|
||||
} else if s[0] == '+' {
|
||||
s = s[1:]
|
||||
}
|
||||
if strings.HasSuffix(s, "()") {
|
||||
s = s[0 : len(s)-2]
|
||||
}
|
||||
parts = strings.SplitN(s, ".", 2)
|
||||
if num, err = strconv.ParseInt(parts[0], 10, 64); err != nil {
|
||||
return
|
||||
}
|
||||
if len(parts) == 1 {
|
||||
f = newFraction(sign*num, 1)
|
||||
} else if len(parts) == 2 {
|
||||
subParts := strings.SplitN(parts[1], "(", 2)
|
||||
if len(subParts) == 1 {
|
||||
den = 1
|
||||
dec := parts[1]
|
||||
lsd := len(dec)
|
||||
for i := lsd - 1; i >= 0 && dec[i] == '0'; i-- {
|
||||
lsd--
|
||||
}
|
||||
for _, c := range dec[0:lsd] {
|
||||
if c < '0' || c > '9' {
|
||||
return nil, errExpectedGot("fract", "digit", c)
|
||||
}
|
||||
num = num*10 + int64(c-'0')
|
||||
den = den * 10
|
||||
}
|
||||
f = newFraction(sign*num, den)
|
||||
} else if len(subParts) == 2 {
|
||||
sub := num
|
||||
mul := int64(1)
|
||||
for _, c := range subParts[0] {
|
||||
if c < '0' || c > '9' {
|
||||
return nil, errExpectedGot("fract", "digit", c)
|
||||
}
|
||||
num = num*10 + int64(c-'0')
|
||||
sub = sub*10 + int64(c-'0')
|
||||
mul *= 10
|
||||
}
|
||||
if len(subParts) == 2 {
|
||||
if s[len(s)-1] != ')' {
|
||||
goto exit
|
||||
}
|
||||
p := subParts[1][0 : len(subParts[1])-1]
|
||||
for _, c := range p {
|
||||
if c < '0' || c > '9' {
|
||||
return nil, errExpectedGot("fract", "digit", c)
|
||||
}
|
||||
num = num*10 + int64(c-'0')
|
||||
den = den*10 + 9
|
||||
}
|
||||
den *= mul
|
||||
}
|
||||
num -= sub
|
||||
f = newFraction(sign*num, den)
|
||||
}
|
||||
}
|
||||
exit:
|
||||
if f == nil {
|
||||
err = errors.New("bad syntax")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *fraction) toFloat() float64 {
|
||||
return float64(f.num) / float64(f.den)
|
||||
}
|
||||
|
||||
func (f *fraction) String() string {
|
||||
return f.ToString(0)
|
||||
}
|
||||
|
||||
func (f *fraction) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
if opt&MultiLine == 0 {
|
||||
sb.WriteString(fmt.Sprintf("%d|%d", f.num, f.den))
|
||||
} else {
|
||||
var s, num string
|
||||
if f.num < 0 && opt&TTY == 0 {
|
||||
num = strconv.FormatInt(-f.num, 10)
|
||||
s = "-"
|
||||
} else {
|
||||
num = strconv.FormatInt(f.num, 10)
|
||||
}
|
||||
den := strconv.FormatInt(f.den, 10)
|
||||
size := max(len(num), len(den))
|
||||
if opt&TTY != 0 {
|
||||
sb.WriteString(fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, s+num)))
|
||||
} else {
|
||||
if len(s) > 0 {
|
||||
sb.WriteString(" ")
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, num)))
|
||||
sb.WriteByte('\n')
|
||||
if len(s) > 0 {
|
||||
sb.WriteString(s)
|
||||
sb.WriteByte(' ')
|
||||
}
|
||||
sb.WriteString(strings.Repeat("-", size))
|
||||
sb.WriteByte('\n')
|
||||
if len(s) > 0 {
|
||||
sb.WriteString(" ")
|
||||
}
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den)))
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (f *fraction) TypeName() string {
|
||||
return "fraction"
|
||||
}
|
||||
|
||||
// -------- fraction term
|
||||
func newFractionTerm(tk *Token) *term {
|
||||
return &term{
|
||||
@@ -252,168 +55,11 @@ func evalFraction(ctx ExprContext, self *term) (v any, err error) {
|
||||
if den == 1 {
|
||||
v = num
|
||||
} else {
|
||||
v = &fraction{num, den}
|
||||
v = &FractionType{num, den}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func gcd(a, b int64) (g int64) {
|
||||
if a < 0 {
|
||||
a = -a
|
||||
}
|
||||
if b < 0 {
|
||||
b = -b
|
||||
}
|
||||
if a < b {
|
||||
a, b = b, a
|
||||
}
|
||||
r := a % b
|
||||
for r > 0 {
|
||||
a, b = b, r
|
||||
r = a % b
|
||||
}
|
||||
g = b
|
||||
return
|
||||
}
|
||||
|
||||
func lcm(a, b int64) (l int64) {
|
||||
g := gcd(a, b)
|
||||
l = a * b / g
|
||||
return
|
||||
}
|
||||
|
||||
func sumFract(f1, f2 *fraction) (sum *fraction) {
|
||||
m := lcm(f1.den, f2.den)
|
||||
sum = newFraction(f1.num*(m/f1.den)+f2.num*(m/f2.den), m)
|
||||
return
|
||||
}
|
||||
|
||||
func mulFract(f1, f2 *fraction) (prod *fraction) {
|
||||
prod = newFraction(f1.num*f2.num, f1.den*f2.den)
|
||||
return
|
||||
}
|
||||
|
||||
func anyToFract(v any) (f *fraction, err error) {
|
||||
var ok bool
|
||||
if f, ok = v.(*fraction); !ok {
|
||||
if n, ok := v.(int64); ok {
|
||||
f = intToFraction(n)
|
||||
}
|
||||
}
|
||||
if f == nil {
|
||||
err = errExpectedGot("fract", typeFraction, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func anyPairToFract(v1, v2 any) (f1, f2 *fraction, err error) {
|
||||
if f1, err = anyToFract(v1); err != nil {
|
||||
return
|
||||
}
|
||||
if f2, err = anyToFract(v2); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func sumAnyFract(af1, af2 any) (sum any, err error) {
|
||||
var f1, f2 *fraction
|
||||
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||
return
|
||||
}
|
||||
f := sumFract(f1, f2)
|
||||
if f.num == 0 {
|
||||
sum = 0
|
||||
} else {
|
||||
sum = simplifyFraction(f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func subAnyFract(af1, af2 any) (sum any, err error) {
|
||||
var f1, f2 *fraction
|
||||
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||
return
|
||||
}
|
||||
f2.num = -f2.num
|
||||
f := sumFract(f1, f2)
|
||||
if f.num == 0 {
|
||||
sum = 0
|
||||
} else {
|
||||
sum = simplifyFraction(f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func mulAnyFract(af1, af2 any) (prod any, err error) {
|
||||
var f1, f2 *fraction
|
||||
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||
return
|
||||
}
|
||||
if f1.num == 0 || f2.num == 0 {
|
||||
prod = 0
|
||||
} else {
|
||||
f := &fraction{f1.num * f2.num, f1.den * f2.den}
|
||||
prod = simplifyFraction(f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func divAnyFract(af1, af2 any) (quot any, err error) {
|
||||
var f1, f2 *fraction
|
||||
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
||||
return
|
||||
}
|
||||
if f2.num == 0 {
|
||||
err = errors.New("division by zero")
|
||||
return
|
||||
return
|
||||
}
|
||||
if f1.num == 0 || f2.den == 0 {
|
||||
quot = 0
|
||||
} else {
|
||||
f := &fraction{f1.num * f2.den, f1.den * f2.num}
|
||||
quot = simplifyFraction(f)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func simplifyFraction(f *fraction) (v any) {
|
||||
f.num, f.den = simplifyIntegers(f.num, f.den)
|
||||
if f.den == 1 {
|
||||
v = f.num
|
||||
} else {
|
||||
v = &fraction{f.num, f.den}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func simplifyIntegers(num, den int64) (a, b int64) {
|
||||
if num == 0 {
|
||||
return 0, 1
|
||||
}
|
||||
if den == 0 {
|
||||
panic("fraction with denominator == 0")
|
||||
}
|
||||
if den < 0 {
|
||||
den = -den
|
||||
num = -num
|
||||
}
|
||||
g := gcd(num, den)
|
||||
a = num / g
|
||||
b = den / g
|
||||
return
|
||||
}
|
||||
|
||||
func intToFraction(n int64) *fraction {
|
||||
return &fraction{n, 1}
|
||||
}
|
||||
|
||||
func isFraction(v any) (ok bool) {
|
||||
_, ok = v.(*fraction)
|
||||
return ok
|
||||
}
|
||||
|
||||
// init
|
||||
func init() {
|
||||
registerTermConstructor(SymVertBar, newFractionTerm)
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ func evalIn(ctx ExprContext, self *term) (v any, err error) {
|
||||
|
||||
if IsList(rightValue) {
|
||||
list, _ := rightValue.(*ListType)
|
||||
v = list.indexDeepCmp(leftValue) >= 0
|
||||
v = list.indexDeepSameCmp(leftValue) >= 0
|
||||
} else if IsDict(rightValue) {
|
||||
dict, _ := rightValue.(*DictType)
|
||||
v = dict.hasKey(leftValue)
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// operator-index.go
|
||||
package expr
|
||||
|
||||
// -------- index term
|
||||
func newIndexTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
children: make([]*term, 0, 2),
|
||||
position: posInfix,
|
||||
priority: priDot,
|
||||
evalFunc: evalIndex,
|
||||
}
|
||||
}
|
||||
|
||||
func verifyKey(indexTerm *term, indexList *ListType) (index any, err error) {
|
||||
index = (*indexList)[0]
|
||||
return
|
||||
}
|
||||
|
||||
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 < 0 && v >= -maxValue {
|
||||
v = maxValue + v
|
||||
}
|
||||
if v >= 0 && v < maxValue {
|
||||
index = v
|
||||
} else {
|
||||
err = indexTerm.Errorf("index %d out of bounds", v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func verifyRange(indexTerm *term, indexList *ListType, maxValue int) (startIndex, endIndex int, err error) {
|
||||
v, _ := ((*indexList)[0]).(*intPair)
|
||||
startIndex = v.a
|
||||
endIndex = v.b
|
||||
if startIndex < 0 && startIndex >= -maxValue {
|
||||
startIndex = maxValue + startIndex
|
||||
}
|
||||
if endIndex < 0 && endIndex >= -maxValue {
|
||||
endIndex = maxValue + endIndex + 1
|
||||
}
|
||||
if startIndex < 0 || startIndex > maxValue {
|
||||
err = indexTerm.Errorf("range start-index %d is out of bounds", startIndex)
|
||||
} else if endIndex < 0 || endIndex > maxValue {
|
||||
err = indexTerm.Errorf("range end-index %d is out of bounds", endIndex)
|
||||
} else if startIndex > endIndex {
|
||||
err = indexTerm.Errorf("range start-index %d must not be greater than end-index %d", startIndex, endIndex)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func evalIndex(ctx ExprContext, self *term) (v any, err error) {
|
||||
var leftValue, rightValue any
|
||||
var indexList *ListType
|
||||
var ok bool
|
||||
|
||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
indexTerm := self.children[1]
|
||||
if indexList, ok = rightValue.(*ListType); !ok {
|
||||
err = self.Errorf("invalid index expression")
|
||||
return
|
||||
} else if len(*indexList) != 1 {
|
||||
err = indexTerm.Errorf("one index only is allowed")
|
||||
return
|
||||
}
|
||||
|
||||
if IsInteger((*indexList)[0]) {
|
||||
switch unboxedValue := leftValue.(type) {
|
||||
case *ListType:
|
||||
var index int
|
||||
if index, err = verifyIndex(indexTerm, indexList, len(*unboxedValue)); err == nil {
|
||||
v = (*unboxedValue)[index]
|
||||
}
|
||||
case string:
|
||||
var index int
|
||||
if index, err = verifyIndex(indexTerm, indexList, len(unboxedValue)); err == nil {
|
||||
v = string(unboxedValue[index])
|
||||
}
|
||||
case *DictType:
|
||||
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)
|
||||
}
|
||||
}
|
||||
default:
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
} else if isIntPair((*indexList)[0]) {
|
||||
switch unboxedValue := leftValue.(type) {
|
||||
case *ListType:
|
||||
var start, end int
|
||||
if start, end, err = verifyRange(indexTerm, indexList, len(*unboxedValue)); err == nil {
|
||||
sublist := ListType((*unboxedValue)[start:end])
|
||||
v = &sublist
|
||||
}
|
||||
case string:
|
||||
var start, end int
|
||||
if start, end, err = verifyRange(indexTerm, indexList, len(unboxedValue)); err == nil {
|
||||
v = unboxedValue[start:end]
|
||||
}
|
||||
default:
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// init
|
||||
func init() {
|
||||
registerTermConstructor(SymIndex, newIndexTerm)
|
||||
}
|
||||
+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, _ = ToInt(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)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// operator-range.go
|
||||
package expr
|
||||
|
||||
import "fmt"
|
||||
|
||||
// -------- range term
|
||||
type intPair struct {
|
||||
a, b int
|
||||
}
|
||||
|
||||
func (p *intPair) TypeName() string {
|
||||
return TypePair
|
||||
}
|
||||
|
||||
func (p *intPair) ToString(opt FmtOpt) string {
|
||||
return fmt.Sprintf("(%d, %d)", p.a, p.b)
|
||||
}
|
||||
|
||||
func isIntPair(v any) bool {
|
||||
_, ok := v.(*intPair)
|
||||
return ok
|
||||
}
|
||||
|
||||
func newRangeTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
children: make([]*term, 0, 2),
|
||||
position: posInfix,
|
||||
priority: priDot,
|
||||
evalFunc: evalRange,
|
||||
}
|
||||
}
|
||||
|
||||
func evalRange(ctx ExprContext, self *term) (v any, err error) {
|
||||
var leftValue, rightValue any
|
||||
|
||||
// if err = self.checkOperands(); err != nil {
|
||||
// return
|
||||
// }
|
||||
if len(self.children) == 0 {
|
||||
leftValue = int64(0)
|
||||
rightValue = int64(-1)
|
||||
} else if len(self.children) == 1 {
|
||||
if leftValue, err = self.children[0].compute(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
rightValue = int64(-1)
|
||||
} else if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
if !(IsInteger(leftValue) && IsInteger(rightValue)) {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
return
|
||||
}
|
||||
|
||||
startIndex, _ := leftValue.(int64)
|
||||
endIndex, _ := rightValue.(int64)
|
||||
|
||||
v = &intPair{int(startIndex), int(endIndex)}
|
||||
return
|
||||
}
|
||||
|
||||
// init
|
||||
func init() {
|
||||
registerTermConstructor(SymColon, newRangeTerm)
|
||||
}
|
||||
+90
-88
@@ -4,13 +4,13 @@
|
||||
// operator-rel.go
|
||||
package expr
|
||||
|
||||
import "reflect"
|
||||
|
||||
//-------- equal term
|
||||
|
||||
func newEqualTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
// class: classOperator,
|
||||
// kind: kindBool,
|
||||
children: make([]*term, 0, 2),
|
||||
position: posInfix,
|
||||
priority: priRelational,
|
||||
@@ -18,6 +18,33 @@ func newEqualTerm(tk *Token) (inst *term) {
|
||||
}
|
||||
}
|
||||
|
||||
type deepFuncTemplate func(a, b any) (eq bool, err error)
|
||||
|
||||
func equals(a, b any, deepCmp deepFuncTemplate) (eq bool, err error) {
|
||||
if isNumOrFract(a) && isNumOrFract(b) {
|
||||
if IsNumber(a) && IsNumber(b) {
|
||||
if IsInteger(a) && IsInteger(b) {
|
||||
li, _ := a.(int64)
|
||||
ri, _ := b.(int64)
|
||||
eq = li == ri
|
||||
} else {
|
||||
eq = numAsFloat(a) == numAsFloat(b)
|
||||
}
|
||||
} else {
|
||||
var cmp int
|
||||
if cmp, err = cmpAnyFract(a, b); err == nil {
|
||||
eq = cmp == 0
|
||||
}
|
||||
}
|
||||
} else if deepCmp != nil && IsList(a) && IsList(b) {
|
||||
eq, err = deepCmp(a, b)
|
||||
} else {
|
||||
eq = reflect.DeepEqual(a, b)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func evalEqual(ctx ExprContext, self *term) (v any, err error) {
|
||||
var leftValue, rightValue any
|
||||
|
||||
@@ -25,21 +52,7 @@ func evalEqual(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if IsNumber(leftValue) && IsNumber(rightValue) {
|
||||
if IsInteger(leftValue) && IsInteger(rightValue) {
|
||||
li, _ := leftValue.(int64)
|
||||
ri, _ := rightValue.(int64)
|
||||
v = li == ri
|
||||
} else {
|
||||
v = numAsFloat(leftValue) == numAsFloat(rightValue)
|
||||
}
|
||||
} else if IsString(leftValue) && IsString(rightValue) {
|
||||
ls, _ := leftValue.(string)
|
||||
rs, _ := rightValue.(string)
|
||||
v = ls == rs
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
v, err = equals(leftValue, rightValue, nil)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -48,8 +61,6 @@ func evalEqual(ctx ExprContext, self *term) (v any, err error) {
|
||||
func newNotEqualTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
// class: classOperator,
|
||||
// kind: kindBool,
|
||||
children: make([]*term, 0, 2),
|
||||
position: posInfix,
|
||||
priority: priRelational,
|
||||
@@ -59,44 +70,17 @@ 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
|
||||
}
|
||||
|
||||
// func evalNotEqual(ctx exprContext, self *term) (v any, err error) {
|
||||
// var leftValue, rightValue any
|
||||
|
||||
// if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
// if isNumber(leftValue) && isNumber(rightValue) {
|
||||
// if isInteger(leftValue) && isInteger(rightValue) {
|
||||
// li, _ := leftValue.(int64)
|
||||
// ri, _ := rightValue.(int64)
|
||||
// v = li != ri
|
||||
// } else {
|
||||
// v = numAsFloat(leftValue) != numAsFloat(rightValue)
|
||||
// }
|
||||
// } else if isString(leftValue) && isString(rightValue) {
|
||||
// ls, _ := leftValue.(string)
|
||||
// rs, _ := rightValue.(string)
|
||||
// v = ls != rs
|
||||
// } else {
|
||||
// err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
//-------- less term
|
||||
|
||||
func newLessTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
// class: classOperator,
|
||||
// kind: kindBool,
|
||||
children: make([]*term, 0, 2),
|
||||
position: posInfix,
|
||||
priority: priRelational,
|
||||
@@ -104,28 +88,44 @@ func newLessTerm(tk *Token) (inst *term) {
|
||||
}
|
||||
}
|
||||
|
||||
func lessThan(self *term, a, b any) (isLess bool, err error) {
|
||||
if isNumOrFract(a) && isNumOrFract(b) {
|
||||
if IsNumber(a) && IsNumber(b) {
|
||||
if IsInteger(a) && IsInteger(b) {
|
||||
li, _ := a.(int64)
|
||||
ri, _ := b.(int64)
|
||||
isLess = li < ri
|
||||
} else {
|
||||
isLess = numAsFloat(a) < numAsFloat(b)
|
||||
}
|
||||
} else {
|
||||
var cmp int
|
||||
if cmp, err = cmpAnyFract(a, b); err == nil {
|
||||
isLess = cmp < 0
|
||||
}
|
||||
}
|
||||
} else if IsString(a) && IsString(b) {
|
||||
ls, _ := a.(string)
|
||||
rs, _ := b.(string)
|
||||
isLess = ls < rs
|
||||
// Inclusion test
|
||||
} else if IsList(a) && IsList(b) {
|
||||
aList, _ := a.(*ListType)
|
||||
bList, _ := b.(*ListType)
|
||||
isLess = len(*aList) < len(*bList) && bList.contains(aList)
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(a, b)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func evalLess(ctx ExprContext, self *term) (v any, err error) {
|
||||
var leftValue, rightValue any
|
||||
|
||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if IsNumber(leftValue) && IsNumber(rightValue) {
|
||||
if IsInteger(leftValue) && IsInteger(rightValue) {
|
||||
li, _ := leftValue.(int64)
|
||||
ri, _ := rightValue.(int64)
|
||||
v = li < ri
|
||||
} else {
|
||||
v = numAsFloat(leftValue) < numAsFloat(rightValue)
|
||||
}
|
||||
} else if IsString(leftValue) && IsString(rightValue) {
|
||||
ls, _ := leftValue.(string)
|
||||
rs, _ := rightValue.(string)
|
||||
v = ls < rs
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
v, err = lessThan(self, leftValue, rightValue)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -141,6 +141,19 @@ func newLessEqualTerm(tk *Token) (inst *term) {
|
||||
}
|
||||
}
|
||||
|
||||
func lessThanOrEqual(self *term, a, b any) (isLessEq bool, err error) {
|
||||
if isLessEq, err = lessThan(self, a, b); err == nil {
|
||||
if !isLessEq {
|
||||
if IsList(a) && IsList(b) {
|
||||
isLessEq, err = sameContent(a, b)
|
||||
} else {
|
||||
isLessEq, err = equals(a, b, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
|
||||
var leftValue, rightValue any
|
||||
|
||||
@@ -148,21 +161,8 @@ func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if IsNumber(leftValue) && IsNumber(rightValue) {
|
||||
if IsInteger(leftValue) && IsInteger(rightValue) {
|
||||
li, _ := leftValue.(int64)
|
||||
ri, _ := rightValue.(int64)
|
||||
v = li <= ri
|
||||
} else {
|
||||
v = numAsFloat(leftValue) <= numAsFloat(rightValue)
|
||||
}
|
||||
} else if IsString(leftValue) && IsString(rightValue) {
|
||||
ls, _ := leftValue.(string)
|
||||
rs, _ := rightValue.(string)
|
||||
v = ls <= rs
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
v, err = lessThanOrEqual(self, leftValue, rightValue)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -171,8 +171,6 @@ func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
|
||||
func newGreaterTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
// class: classOperator,
|
||||
// kind: kindBool,
|
||||
children: make([]*term, 0, 2),
|
||||
position: posInfix,
|
||||
priority: priRelational,
|
||||
@@ -181,10 +179,13 @@ func newGreaterTerm(tk *Token) (inst *term) {
|
||||
}
|
||||
|
||||
func evalGreater(ctx ExprContext, self *term) (v any, err error) {
|
||||
if v, err = evalLessEqual(ctx, self); err == nil {
|
||||
b, _ := toBool(v)
|
||||
v = !b
|
||||
var leftValue, rightValue any
|
||||
|
||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
v, err = lessThan(self, rightValue, leftValue)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -193,8 +194,6 @@ func evalGreater(ctx ExprContext, self *term) (v any, err error) {
|
||||
func newGreaterEqualTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
// class: classOperator,
|
||||
// kind: kindBool,
|
||||
children: make([]*term, 0, 2),
|
||||
position: posInfix,
|
||||
priority: priRelational,
|
||||
@@ -203,10 +202,13 @@ func newGreaterEqualTerm(tk *Token) (inst *term) {
|
||||
}
|
||||
|
||||
func evalGreaterEqual(ctx ExprContext, self *term) (v any, err error) {
|
||||
if v, err = evalLess(ctx, self); err == nil {
|
||||
b, _ := toBool(v)
|
||||
v = !b
|
||||
var leftValue, rightValue any
|
||||
|
||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
v, err = lessThanOrEqual(self, rightValue, leftValue)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -38,23 +38,6 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
|
||||
rightInt, _ := rightValue.(int64)
|
||||
v = leftInt + rightInt
|
||||
}
|
||||
// } else if IsList(leftValue) || IsList(rightValue) {
|
||||
// var leftList, rightList *ListType
|
||||
// var ok bool
|
||||
// if leftList, ok = leftValue.(*ListType); !ok {
|
||||
// leftList = &ListType{leftValue}
|
||||
// }
|
||||
// if rightList, ok = rightValue.(*ListType); !ok {
|
||||
// rightList = &ListType{rightValue}
|
||||
// }
|
||||
// sumList := make(ListType, 0, len(*leftList)+len(*rightList))
|
||||
// for _, item := range *leftList {
|
||||
// sumList = append(sumList, item)
|
||||
// }
|
||||
// for _, item := range *rightList {
|
||||
// sumList = append(sumList, item)
|
||||
// }
|
||||
// v = &sumList
|
||||
} else if IsList(leftValue) && IsList(rightValue) {
|
||||
var leftList, rightList *ListType
|
||||
leftList, _ = leftValue.(*ListType)
|
||||
|
||||
@@ -6,51 +6,41 @@ 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
|
||||
}
|
||||
|
||||
func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token) (tree *term, err error) {
|
||||
// name, _ := tk.Value.(string)
|
||||
// funcObj := self.ctx.GetFuncInfo(name)
|
||||
// if funcObj == nil {
|
||||
// err = fmt.Errorf("unknown function %s()", name)
|
||||
// return
|
||||
// }
|
||||
// maxArgs := funcObj.MaxArgs()
|
||||
// if maxArgs < 0 {
|
||||
// maxArgs = funcObj.MinArgs() + 10
|
||||
// }
|
||||
// args := make([]*term, 0, maxArgs)
|
||||
args := make([]*term, 0, 10)
|
||||
itemExpected := false
|
||||
lastSym := SymUnknown
|
||||
for lastSym != SymClosedRound && lastSym != SymEos {
|
||||
var subTree *ast
|
||||
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil {
|
||||
if subTree.root != nil {
|
||||
args = append(args, subTree.root)
|
||||
}
|
||||
} else {
|
||||
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err != nil {
|
||||
break
|
||||
}
|
||||
prev := scanner.Previous()
|
||||
if subTree.root != nil {
|
||||
args = append(args, subTree.root)
|
||||
} else if itemExpected {
|
||||
err = prev.ErrorExpectedGot("function-param-value")
|
||||
break
|
||||
}
|
||||
|
||||
itemExpected = prev.Sym == SymComma
|
||||
lastSym = scanner.Previous().Sym
|
||||
}
|
||||
if err == nil {
|
||||
// TODO Check arguments
|
||||
if lastSym != SymClosedRound {
|
||||
err = errors.New("unterminate arguments list")
|
||||
err = errors.New("unterminated arguments list")
|
||||
} else {
|
||||
tree = newFuncCallTerm(tk, args)
|
||||
}
|
||||
@@ -58,62 +48,12 @@ func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token)
|
||||
return
|
||||
}
|
||||
|
||||
// func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
||||
// // Example: "add = func(x,y) {x+y}
|
||||
// var body *ast
|
||||
// args := make([]*term, 0)
|
||||
// lastSym := SymUnknown
|
||||
// itemExpected := false
|
||||
// tk := scanner.Previous()
|
||||
// for lastSym != SymClosedRound && lastSym != SymEos {
|
||||
// var subTree *ast
|
||||
// if subTree, err = self.parseItem(scanner, true, SymComma, SymClosedRound); err == nil {
|
||||
// if subTree.root != nil {
|
||||
// if subTree.root.symbol() == SymIdentifier {
|
||||
// args = append(args, subTree.root)
|
||||
// } else {
|
||||
// err = tk.ErrorExpectedGotString("param-name", subTree.root.String())
|
||||
// }
|
||||
// } else if itemExpected {
|
||||
// prev := scanner.Previous()
|
||||
// err = prev.ErrorExpectedGot("function-param")
|
||||
// break
|
||||
// }
|
||||
// } else {
|
||||
// break
|
||||
// }
|
||||
// lastSym = scanner.Previous().Sym
|
||||
// itemExpected = lastSym == SymComma
|
||||
// }
|
||||
|
||||
// if err == nil && lastSym != SymClosedRound {
|
||||
// err = tk.ErrorExpectedGot(")")
|
||||
// }
|
||||
// if err == nil {
|
||||
// tk = scanner.Next()
|
||||
// if tk.Sym == SymOpenBrace {
|
||||
// body, err = self.parseGeneral(scanner, true, true, SymClosedBrace)
|
||||
// } else {
|
||||
// err = tk.ErrorExpectedGot("{")
|
||||
// }
|
||||
// }
|
||||
// if err == nil {
|
||||
// // TODO Check arguments
|
||||
// if scanner.Previous().Sym != SymClosedBrace {
|
||||
// err = scanner.Previous().ErrorExpectedGot("}")
|
||||
// } else {
|
||||
// tk = scanner.makeValueToken(SymExpression, "", body)
|
||||
// tree = newFuncDefTerm(tk, args)
|
||||
// }
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
||||
// Example: "add = func(x,y) {x+y}
|
||||
var body *ast
|
||||
args := make([]*term, 0)
|
||||
lastSym := SymUnknown
|
||||
defaultParamsStarted := false
|
||||
itemExpected := false
|
||||
tk := scanner.Previous()
|
||||
for lastSym != SymClosedRound && lastSym != SymEos {
|
||||
@@ -122,9 +62,20 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
||||
param := newTerm(tk)
|
||||
args = append(args, param)
|
||||
tk = scanner.Next()
|
||||
if tk.Sym == SymEqual {
|
||||
var paramExpr *ast
|
||||
defaultParamsStarted = true
|
||||
if paramExpr, err = self.parseItem(scanner, false, SymComma, SymClosedRound); err != nil {
|
||||
break
|
||||
}
|
||||
param.forceChild(paramExpr.root)
|
||||
} else if defaultParamsStarted {
|
||||
err = tk.Errorf("can't mix default and non-default parameters")
|
||||
break
|
||||
}
|
||||
} else if itemExpected {
|
||||
prev := scanner.Previous()
|
||||
err = prev.ErrorExpectedGot("function-param")
|
||||
err = prev.ErrorExpectedGot("function-param-spec")
|
||||
break
|
||||
}
|
||||
lastSym = scanner.Previous().Sym
|
||||
@@ -143,7 +94,6 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
// TODO Check arguments
|
||||
if scanner.Previous().Sym != SymClosedBrace {
|
||||
err = scanner.Previous().ErrorExpectedGot("}")
|
||||
} else {
|
||||
@@ -154,15 +104,34 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
||||
func (self *parser) parseList(scanner *scanner, parsingIndeces bool, allowVarRef bool) (subtree *term, err error) {
|
||||
r, c := scanner.lastPos()
|
||||
args := make([]*term, 0)
|
||||
lastSym := SymUnknown
|
||||
itemExpected := false
|
||||
for lastSym != SymClosedSquare && lastSym != SymEos {
|
||||
var subTree *ast
|
||||
zeroRequired := scanner.current.Sym == SymColon
|
||||
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
|
||||
if subTree.root != nil {
|
||||
args = append(args, subTree.root)
|
||||
root := subTree.root
|
||||
if root != nil {
|
||||
if !parsingIndeces && root.symbol() == SymColon {
|
||||
err = root.Errorf("unexpected range expression")
|
||||
break
|
||||
}
|
||||
args = append(args, root)
|
||||
if parsingIndeces && root.symbol() == SymColon && zeroRequired { //len(root.children) == 0 {
|
||||
if len(root.children) == 1 {
|
||||
root.children = append(root.children, root.children[0])
|
||||
} else if len(root.children) > 1 {
|
||||
err = root.Errorf("invalid range specification")
|
||||
break
|
||||
}
|
||||
zeroTk := NewValueToken(root.tk.row, root.tk.col, SymInteger, "0", int64(0))
|
||||
zeroTerm := newTerm(zeroTk)
|
||||
zeroTerm.setParent(root)
|
||||
root.children[0] = zeroTerm
|
||||
}
|
||||
} else if itemExpected {
|
||||
prev := scanner.Previous()
|
||||
err = prev.ErrorExpectedGot("list-item")
|
||||
@@ -175,11 +144,10 @@ func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term
|
||||
itemExpected = lastSym == SymComma
|
||||
}
|
||||
if err == nil {
|
||||
// TODO Check arguments
|
||||
if lastSym != SymClosedSquare {
|
||||
err = scanner.Previous().ErrorExpectedGot("]")
|
||||
} else {
|
||||
subtree = newListTerm(args)
|
||||
subtree = newListTerm(r, c, args)
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -207,7 +175,6 @@ func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *t
|
||||
itemExpected = lastSym == SymComma
|
||||
}
|
||||
if err == nil {
|
||||
// TODO Check arguments
|
||||
if lastSym != SymClosedRound {
|
||||
err = scanner.Previous().ErrorExpectedGot(")")
|
||||
} else {
|
||||
@@ -234,7 +201,6 @@ func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, e
|
||||
key = tk.Value
|
||||
}
|
||||
} else {
|
||||
// err = tk.Errorf("expected dictionary key or closed brace, got %q", tk)
|
||||
err = tk.ErrorExpectedGot("dictionary-key or }")
|
||||
}
|
||||
return
|
||||
@@ -272,7 +238,6 @@ func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree
|
||||
itemExpected = lastSym == SymComma
|
||||
}
|
||||
if err == nil {
|
||||
// TODO Check arguments
|
||||
if lastSym != SymClosedBrace {
|
||||
err = scanner.Previous().ErrorExpectedGot("}")
|
||||
} else {
|
||||
@@ -294,14 +259,14 @@ func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaul
|
||||
err = tk.Errorf("case list in default clause")
|
||||
return
|
||||
}
|
||||
if filterList, err = self.parseList(scanner, allowVarRef); err != nil {
|
||||
if filterList, err = self.parseList(scanner, false, allowVarRef); err != nil {
|
||||
return
|
||||
}
|
||||
tk = scanner.Next()
|
||||
startRow = tk.row
|
||||
startCol = tk.col
|
||||
} else if !defaultCase {
|
||||
filterList = newListTerm(make([]*term, 0))
|
||||
filterList = newListTerm(startRow, startCol, make([]*term, 0))
|
||||
}
|
||||
|
||||
if tk.Sym == SymOpenBrace {
|
||||
@@ -352,13 +317,21 @@ func (self *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, e
|
||||
return self.parseGeneral(scanner, true, false, termSymbols...)
|
||||
}
|
||||
|
||||
func couldBeACollection(t *term) bool {
|
||||
var sym = SymUnknown
|
||||
if t != nil {
|
||||
sym = t.symbol()
|
||||
}
|
||||
return sym == SymList || sym == SymString || sym == SymDict || sym == SymExpression || sym == SymVariable
|
||||
}
|
||||
|
||||
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
|
||||
// lastSym := SymUnknown
|
||||
for tk = scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); tk = scanner.Next() {
|
||||
if tk.Sym == SymComment {
|
||||
continue
|
||||
@@ -368,6 +341,8 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
||||
if allowForest {
|
||||
tree.ToForest()
|
||||
firstToken = true
|
||||
currentTerm = nil
|
||||
selectorTerm = nil
|
||||
continue
|
||||
} else {
|
||||
err = tk.Errorf(`unexpected token %q, expected ",", "]", or ")"`, tk.source)
|
||||
@@ -401,20 +376,33 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
||||
}
|
||||
case SymOpenSquare:
|
||||
var listTerm *term
|
||||
if listTerm, err = self.parseList(scanner, allowVarRef); err == nil {
|
||||
parsingIndeces := couldBeACollection(currentTerm)
|
||||
if listTerm, err = self.parseList(scanner, parsingIndeces, allowVarRef); err == nil {
|
||||
if parsingIndeces {
|
||||
indexTk := NewToken(listTerm.tk.row, listTerm.tk.col, SymIndex, listTerm.source())
|
||||
indexTerm := newTerm(indexTk)
|
||||
if err = tree.addTerm(indexTerm); err == nil {
|
||||
err = tree.addTerm(listTerm)
|
||||
}
|
||||
} else {
|
||||
err = tree.addTerm(listTerm)
|
||||
}
|
||||
currentTerm = listTerm
|
||||
}
|
||||
case SymOpenBrace:
|
||||
if currentTerm != nil && currentTerm.symbol() == SymColon {
|
||||
err = currentTerm.Errorf(`selector-case outside of a selector context`)
|
||||
} else {
|
||||
var mapTerm *term
|
||||
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
|
||||
err = tree.addTerm(mapTerm)
|
||||
currentTerm = mapTerm
|
||||
}
|
||||
case SymEqual:
|
||||
if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
|
||||
currentTerm, err = tree.addToken2(tk)
|
||||
}
|
||||
case SymEqual:
|
||||
// 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 {
|
||||
@@ -439,15 +427,17 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
||||
}
|
||||
case SymColon, SymDoubleColon:
|
||||
var caseTerm *term
|
||||
if selectorTerm == nil {
|
||||
err = tk.Errorf("selector-case outside of a selector context")
|
||||
} else if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
|
||||
if selectorTerm != nil {
|
||||
if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
|
||||
addSelectorCase(selectorTerm, caseTerm)
|
||||
currentTerm = caseTerm
|
||||
if tk.Sym == SymDoubleColon {
|
||||
selectorTerm = nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currentTerm, err = tree.addToken2(tk)
|
||||
}
|
||||
default:
|
||||
currentTerm, err = tree.addToken2(tk)
|
||||
}
|
||||
@@ -456,7 +446,7 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
||||
selectorTerm = nil
|
||||
|
||||
}
|
||||
lastSym = tk.Sym
|
||||
// lastSym = tk.Sym
|
||||
}
|
||||
if err == nil {
|
||||
err = tk.Error()
|
||||
@@ -464,9 +454,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
|
||||
// }
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// plugin.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"plugin"
|
||||
)
|
||||
|
||||
var pluginRegister map[string]*plugin.Plugin
|
||||
|
||||
func registerPlugin(name string, p *plugin.Plugin) (err error) {
|
||||
if pluginExists(name) {
|
||||
err = fmt.Errorf("plugin %q already loaded", name)
|
||||
} else {
|
||||
pluginRegister[name] = p
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func pluginExists(name string) (exists bool) {
|
||||
_, exists = pluginRegister[name]
|
||||
return
|
||||
}
|
||||
|
||||
func importPlugin( /*ctx ExprContext,*/ dirList []string, name string) (err error) {
|
||||
var filePath string
|
||||
var p *plugin.Plugin
|
||||
var sym plugin.Symbol
|
||||
var moduleName string
|
||||
var importFunc func(ExprContext)
|
||||
var ok bool
|
||||
|
||||
decoratedName := fmt.Sprintf("expr-%s-plugin.so", name)
|
||||
|
||||
if filePath, err = makeFilepath(decoratedName, dirList); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if p, err = plugin.Open(filePath); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if sym, err = p.Lookup("MODULE_NAME"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if moduleName = *sym.(*string); moduleName == "" {
|
||||
err = fmt.Errorf("plugin %q does not provide a valid module name", decoratedName)
|
||||
return
|
||||
}
|
||||
|
||||
if sym, err = p.Lookup("DEPENDENCIES"); err != nil {
|
||||
return
|
||||
}
|
||||
if deps := *sym.(*[]string); len(deps) > 0 {
|
||||
// var count int
|
||||
if err = loadModules(dirList, deps); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if sym, err = p.Lookup("ImportFuncs"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if importFunc, ok = sym.(func(ExprContext)); !ok {
|
||||
err = fmt.Errorf("plugin %q does not provide a valid import function", decoratedName)
|
||||
return
|
||||
}
|
||||
|
||||
registerPlugin(moduleName, p)
|
||||
importFunc(globalCtx)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func loadModules(dirList []string, moduleNames []string) (err error) {
|
||||
for _, name := range moduleNames {
|
||||
if err1 := importPlugin(dirList, name); err1 != nil {
|
||||
if !ImportInContext(name) {
|
||||
err = err1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func init() {
|
||||
pluginRegister = make(map[string]*plugin.Plugin)
|
||||
}
|
||||
+16
-3
@@ -74,6 +74,14 @@ func (self *scanner) unreadChar() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (self *scanner) lastPos() (r, c int) {
|
||||
if self.prev != nil {
|
||||
r = self.prev.row
|
||||
c = self.prev.col
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *scanner) Previous() *Token {
|
||||
return self.prev
|
||||
}
|
||||
@@ -170,13 +178,18 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
||||
tk = self.makeToken(SymDot, ch)
|
||||
}
|
||||
case '\'':
|
||||
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)
|
||||
@@ -525,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
|
||||
@@ -546,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,142 +0,0 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// simple-func-store.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SimpleFuncStore struct {
|
||||
SimpleVarStore
|
||||
funcStore map[string]*funcInfo
|
||||
}
|
||||
|
||||
type funcInfo struct {
|
||||
name string
|
||||
minArgs int
|
||||
maxArgs int
|
||||
functor Functor
|
||||
}
|
||||
|
||||
func (info *funcInfo) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
var i int
|
||||
sb.WriteString("func(")
|
||||
for i = 0; i < info.minArgs; i++ {
|
||||
if i > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("arg%d", i+1))
|
||||
}
|
||||
for ; i < info.maxArgs; i++ {
|
||||
sb.WriteString(fmt.Sprintf("arg%d", i+1))
|
||||
}
|
||||
if info.maxArgs < 0 {
|
||||
if info.minArgs > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString("...")
|
||||
}
|
||||
sb.WriteString(") {...}")
|
||||
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
|
||||
for _, name := range ctx.EnumFuncs(func(name string) bool { return true }) {
|
||||
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) 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
|
||||
}
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// simple-store.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
// "strings"
|
||||
)
|
||||
|
||||
type SimpleStore struct {
|
||||
parent ExprContext
|
||||
varStore map[string]any
|
||||
funcStore map[string]ExprFunc
|
||||
}
|
||||
|
||||
func NewSimpleStore() *SimpleStore {
|
||||
ctx := &SimpleStore{
|
||||
varStore: make(map[string]any),
|
||||
funcStore: make(map[string]ExprFunc),
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func filterRefName(name string) bool { return name[0] != '@' }
|
||||
func filterPrivName(name string) bool { return name[0] != '_' }
|
||||
|
||||
func (ctx *SimpleStore) SetParent(parentCtx ExprContext) {
|
||||
ctx.parent = parentCtx
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) GetParent() ExprContext {
|
||||
return ctx.parent
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) Clone() ExprContext {
|
||||
return &SimpleStore{
|
||||
varStore: CloneFilteredMap(ctx.varStore, filterRefName),
|
||||
funcStore: CloneFilteredMap(ctx.funcStore, filterRefName),
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) Merge(src ExprContext) {
|
||||
for _, name := range src.EnumVars(filterRefName) {
|
||||
ctx.varStore[name], _ = src.GetVar(name)
|
||||
}
|
||||
for _, name := range src.EnumFuncs(filterRefName) {
|
||||
ctx.funcStore[name], _ = src.GetFuncInfo(name)
|
||||
}
|
||||
}
|
||||
/*
|
||||
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
|
||||
sb.WriteString("vars: {\n")
|
||||
first := true
|
||||
for _, name := range ctx.EnumVars(nil) {
|
||||
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}")
|
||||
}
|
||||
|
||||
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
|
||||
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))
|
||||
if formatter, ok := value.(Formatter); ok {
|
||||
sb.WriteString(formatter.ToString(0))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v", value))
|
||||
}
|
||||
}
|
||||
sb.WriteString("\n}")
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) ToString(opt FmtOpt) string {
|
||||
var sb strings.Builder
|
||||
varsCtxToBuilder(&sb, ctx, 0)
|
||||
sb.WriteByte('\n')
|
||||
funcsCtxToBuilder(&sb, ctx, 0)
|
||||
return sb.String()
|
||||
}
|
||||
*/
|
||||
|
||||
func (ctx *SimpleStore) ToString(opt FmtOpt) string {
|
||||
dict := ctx.ToDict()
|
||||
return dict.ToString(opt)
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) varsToDict(dict *DictType) *DictType {
|
||||
names := ctx.EnumVars(nil)
|
||||
slices.Sort(names)
|
||||
for _, name := range ctx.EnumVars(nil) {
|
||||
value, _ := ctx.GetVar(name)
|
||||
if f, ok := value.(Formatter); ok {
|
||||
(*dict)[name] = f.ToString(0)
|
||||
} else if _, ok = value.(Functor); ok {
|
||||
(*dict)[name] = "func(){}"
|
||||
} else {
|
||||
(*dict)[name] = fmt.Sprintf("%v", value)
|
||||
}
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) funcsToDict(dict *DictType) *DictType {
|
||||
names := ctx.EnumFuncs(func(name string) bool { return true })
|
||||
slices.Sort(names)
|
||||
for _, name := range names {
|
||||
value, _ := ctx.GetFuncInfo(name)
|
||||
if formatter, ok := value.(Formatter); ok {
|
||||
(*dict)[name] = formatter.ToString(0)
|
||||
} else {
|
||||
(*dict)[name] = fmt.Sprintf("%v", value)
|
||||
}
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) ToDict() (dict *DictType) {
|
||||
dict = MakeDict()
|
||||
(*dict)["variables"] = ctx.varsToDict(MakeDict())
|
||||
(*dict)["functions"] = ctx.funcsToDict(MakeDict())
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) GetVar(varName string) (v any, exists bool) {
|
||||
v, exists = ctx.varStore[varName]
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) UnsafeSetVar(varName string, value any) {
|
||||
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
|
||||
ctx.varStore[varName] = value
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) 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 *SimpleStore) 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 *SimpleStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
|
||||
info, exists = ctx.funcStore[name]
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) RegisterFuncInfo(info ExprFunc) {
|
||||
ctx.funcStore[info.Name()], _ = info.(*funcInfo)
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) RegisterFunc(name string, functor Functor, returnType string, params []ExprFuncParam) (err error) {
|
||||
var info *funcInfo
|
||||
if info, err = newFuncInfo(name, functor, returnType, params); err == nil {
|
||||
ctx.funcStore[name] = info
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx *SimpleStore) 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 *SimpleStore) Call(name string, args []any) (result any, err error) {
|
||||
if info, exists := GetLocalFuncInfo(ctx, name); exists {
|
||||
functor := info.Functor()
|
||||
result, err = functor.Invoke(ctx, name, args)
|
||||
} else {
|
||||
err = fmt.Errorf("unknown function %s()", name)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,121 +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) 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()
|
||||
}
|
||||
@@ -85,6 +85,7 @@ const (
|
||||
SymFuncDef
|
||||
SymList
|
||||
SymDict
|
||||
SymIndex
|
||||
SymExpression
|
||||
SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
|
||||
SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
|
||||
@@ -100,6 +101,7 @@ const (
|
||||
SymKwBut
|
||||
SymKwFunc
|
||||
SymKwBuiltin
|
||||
SymKwPlugin
|
||||
SymKwIn
|
||||
SymKwInclude
|
||||
SymKwNil
|
||||
@@ -112,6 +114,7 @@ func init() {
|
||||
keywords = map[string]Symbol{
|
||||
"AND": SymKwAnd,
|
||||
"BUILTIN": SymKwBuiltin,
|
||||
"PLUGIN": SymKwPlugin,
|
||||
"BUT": SymKwBut,
|
||||
"FUNC": SymKwFunc,
|
||||
"IN": SymKwIn,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// ast_test.go
|
||||
// t_ast_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// t_builtin-base_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFuncBase(t *testing.T) {
|
||||
section := "Builtin-Base"
|
||||
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`isNil(nil)`, true, nil},
|
||||
/* 2 */ {`v=nil; isNil(v)`, true, nil},
|
||||
/* 3 */ {`v=5; isNil(v)`, false, nil},
|
||||
/* 4 */ {`int(true)`, int64(1), nil},
|
||||
/* 5 */ {`int(false)`, int64(0), nil},
|
||||
/* 6 */ {`int(3.1)`, int64(3), nil},
|
||||
/* 7 */ {`int(3.9)`, int64(3), nil},
|
||||
/* 8 */ {`int("432")`, int64(432), nil},
|
||||
/* 9 */ {`int("1.5")`, nil, errors.New(`strconv.Atoi: parsing "1.5": invalid syntax`)},
|
||||
/* 10 */ {`int("432", 4)`, nil, errors.New(`int(): too much params -- expected 1, got 2`)},
|
||||
/* 11 */ {`int(nil)`, nil, errors.New(`int(): can't convert nil to int`)},
|
||||
/* 12 */ {`isInt(2+1)`, true, nil},
|
||||
/* 13 */ {`isInt(3.1)`, false, nil},
|
||||
/* 14 */ {`isFloat(3.1)`, true, nil},
|
||||
/* 15 */ {`isString("3.1")`, true, nil},
|
||||
/* 16 */ {`isString("3" + 1)`, true, nil},
|
||||
/* 17 */ {`isList(["3", 1])`, true, nil},
|
||||
/* 18 */ {`isDict({"a":"3", "b":1})`, true, nil},
|
||||
/* 19 */ {`isFract(1|3)`, true, nil},
|
||||
/* 20 */ {`isFract(3|1)`, false, nil},
|
||||
/* 21 */ {`isRational(3|1)`, true, nil},
|
||||
/* 22 */ {`fract("2.2(3)")`, newFraction(67, 30), nil},
|
||||
/* 23 */ {`fract("1.21(3)")`, newFraction(91, 75), nil},
|
||||
/* 24 */ {`fract(1.21(3))`, newFraction(91, 75), nil},
|
||||
/* 25 */ {`fract(1.21)`, newFraction(121, 100), nil},
|
||||
/* 26 */ {`dec(2)`, float64(2), nil},
|
||||
/* 27 */ {`dec(2.0)`, float64(2), nil},
|
||||
/* 28 */ {`dec("2.0")`, float64(2), nil},
|
||||
/* 29 */ {`dec(true)`, float64(1), nil},
|
||||
/* 30 */ {`dec(true")`, nil, errors.New("[1:11] missing string termination \"")},
|
||||
/* 31 */ {`dec()`, nil, errors.New(`dec(): too few params -- expected 1, got 0`)},
|
||||
/* 32 */ {`dec(1,2,3)`, nil, errors.New(`dec(): too much params -- expected 1, got 3`)},
|
||||
/* 33 */ {`isBool(false)`, true, nil},
|
||||
/* 34 */ {`fract(1|2)`, newFraction(1, 2), nil},
|
||||
/* 35 */ {`fract(12,2)`, newFraction(6, 1), nil},
|
||||
/* 36 */ {`bool(2)`, true, nil},
|
||||
/* 37 */ {`bool(1|2)`, true, nil},
|
||||
/* 38 */ {`bool(1.0)`, true, nil},
|
||||
/* 39 */ {`bool("1")`, true, nil},
|
||||
/* 40 */ {`bool(false)`, false, nil},
|
||||
/* 41 */ {`bool([1])`, nil, errors.New(`bool(): can't convert list to bool`)},
|
||||
/* 42 */ {`dec(false)`, float64(0), nil},
|
||||
/* 43 */ {`dec(1|2)`, float64(0.5), nil},
|
||||
/* 44 */ {`dec([1])`, nil, errors.New(`dec(): can't convert list to float`)},
|
||||
// /* 45 */ {`string([1])`, nil, errors.New(`string(): can't convert list to string`)},
|
||||
}
|
||||
|
||||
t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// parserTestSpec(t, section, inputs, 2)
|
||||
parserTest(t, section, inputs)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// t_builtin-import_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFuncImport(t *testing.T) {
|
||||
section := "Builtin-Import"
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`builtin "import"; import("./test-resources/test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
||||
/* 2 */ {`builtin "import"; import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
||||
/* 3 */ {`builtin "import"; importAll("./test-resources/test-funcs.expr"); six()`, int64(6), nil},
|
||||
/* 4 */ {`builtin "import"; import("./test-resources/sample-export-all.expr"); six()`, int64(6), nil},
|
||||
}
|
||||
|
||||
t.Setenv("EXPR_PATH", "test-resources")
|
||||
|
||||
// parserTestSpec(t, section, inputs, 69)
|
||||
parserTest(t, section, inputs)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// t_builtin-math-arith_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFuncMathArith(t *testing.T) {
|
||||
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},
|
||||
/* 3 */ {`builtin "math.arith"; mulX(1,2,3)`, nil, errors.New(`unknown function mulX()`)},
|
||||
/* 4 */ {`builtin "math.arith"; add(1+4,3+2,5*(3-2))`, int64(15), nil},
|
||||
/* 5 */ {`builtin "math.arith"; add(add(1+4),3+2,5*(3-2))`, int64(15), nil},
|
||||
/* 6 */ {`builtin "math.arith"; add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil},
|
||||
/* 7 */ {`builtin "math.arith"; a=5; b=2; add(a, b*3)`, int64(11), nil},
|
||||
/* 8 */ {`builtin "math.arith"; var2="abc"; add(1,2) but var2`, "abc", nil},
|
||||
}
|
||||
|
||||
// t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// parserTestSpec(t, section, inputs, 69)
|
||||
parserTest(t, section, inputs)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// t_builtin-os-file_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFuncOs(t *testing.T) {
|
||||
section := "Builtin-OS-File"
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`builtin "os.file"`, int64(1), nil},
|
||||
/* 2 */ {`builtin "os.file"; handle=fileOpen("/etc/hosts"); fileClose(handle)`, true, nil},
|
||||
/* 3 */ {`builtin "os.file"; handle=fileOpen("/etc/hostsX")`, nil, errors.New(`open /etc/hostsX: no such file or directory`)},
|
||||
/* 4 */ {`builtin "os.file"; handle=fileCreate("/tmp/dummy"); fileClose(handle)`, true, nil},
|
||||
/* 5 */ {`builtin "os.file"; handle=fileAppend("/tmp/dummy"); fileWrite(handle, "bye-bye"); fileClose(handle)`, true, nil},
|
||||
/* 6 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); word=fileRead(handle, "-"); fileClose(handle);word`, "bye", nil},
|
||||
/* 7 */ {`builtin "os.file"; word=fileRead(nil, "-")`, nil, errors.New(`readFileFunc(): invalid file handle`)},
|
||||
/* 7 */ {`builtin "os.file"; fileWrite(nil, "bye")`, nil, errors.New(`writeFileFunc(): invalid file handle`)},
|
||||
}
|
||||
|
||||
// t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// parserTestSpec(t, section, inputs, 69)
|
||||
parserTest(t, section, inputs)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// t_builtin-string_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFuncString(t *testing.T) {
|
||||
section := "Builtin-String"
|
||||
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`builtin "string"; strJoin("-", "one", "two", "three")`, "one-two-three", nil},
|
||||
/* 2 */ {`builtin "string"; strJoin("-", ["one", "two", "three"])`, "one-two-three", nil},
|
||||
/* 3 */ {`builtin "string"; ls= ["one", "two", "three"]; strJoin("-", ls)`, "one-two-three", nil},
|
||||
/* 4 */ {`builtin "string"; ls= ["one", "two", "three"]; strJoin(1, ls)`, nil, errors.New(`strJoin(): the "separator" parameter must be a string, got a integer (1)`)},
|
||||
/* 5 */ {`builtin "string"; ls= ["one", 2, "three"]; strJoin("-", ls)`, nil, errors.New(`strJoin(): expected string, got integer (2)`)},
|
||||
/* 6 */ {`builtin "string"; "<"+strTrim(" bye bye ")+">"`, "<bye bye>", nil},
|
||||
/* 7 */ {`builtin "string"; strSub("0123456789", 1,2)`, "12", nil},
|
||||
/* 8 */ {`builtin "string"; strSub("0123456789", -3,2)`, "78", nil},
|
||||
/* 9 */ {`builtin "string"; strSub("0123456789", -3)`, "789", nil},
|
||||
/* 10 */ {`builtin "string"; strSub("0123456789")`, "0123456789", nil},
|
||||
/* 11 */ {`builtin "string"; strStartsWith("0123456789", "xyz", "012")`, true, nil},
|
||||
/* 12 */ {`builtin "string"; strStartsWith("0123456789", "xyz", "0125")`, false, nil},
|
||||
/* 13 */ {`builtin "string"; strStartsWith("0123456789")`, nil, errors.New(`strStartsWith(): too few params -- expected 2 or more, got 1`)},
|
||||
/* 14 */ {`builtin "string"; strEndsWith("0123456789", "xyz", "789")`, true, nil},
|
||||
/* 15 */ {`builtin "string"; strEndsWith("0123456789", "xyz", "0125")`, false, nil},
|
||||
/* 16 */ {`builtin "string"; strEndsWith("0123456789")`, nil, errors.New(`strEndsWith(): too few params -- expected 2 or more, got 1`)},
|
||||
/* 17 */ {`builtin "string"; strSplit("one-two-three", "-")`, newListA("one", "two", "three"), nil},
|
||||
/* 18 */ {`builtin "string"; strJoin("-", [1, "two", "three"])`, nil, errors.New(`strJoin(): expected string, got integer (1)`)},
|
||||
/* 19 */ {`builtin "string"; strJoin()`, nil, errors.New(`strJoin(): too few params -- expected 1 or more, got 0`)},
|
||||
|
||||
/* 69 */ /*{`builtin "string"; $$global`, `vars: {
|
||||
}
|
||||
funcs: {
|
||||
add(any=0 ...) -> number,
|
||||
dec(any) -> decimal,
|
||||
endsWithStr(source, suffix) -> boolean,
|
||||
fract(any, denominator=1) -> fraction,
|
||||
import( ...) -> any,
|
||||
importAll( ...) -> any,
|
||||
int(any) -> integer,
|
||||
isBool(any) -> boolean,
|
||||
isDec(any) -> boolean,
|
||||
isDict(any) -> boolean,
|
||||
isFloat(any) -> boolean,
|
||||
isFract(any) -> boolean,
|
||||
isInt(any) -> boolean,
|
||||
isList(any) -> boolean,
|
||||
isNil(any) -> boolean,
|
||||
isString(any) -> boolean,
|
||||
joinStr(separator, item="" ...) -> string,
|
||||
mul(any=1 ...) -> number,
|
||||
splitStr(source, separator="", count=-1) -> list of string,
|
||||
startsWithStr(source, prefix) -> boolean,
|
||||
subStr(source, start=0, count=-1) -> string,
|
||||
trimStr(source) -> string
|
||||
}
|
||||
`, nil},*/
|
||||
}
|
||||
|
||||
//t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// parserTestSpec(t, section, inputs, 19)
|
||||
parserTest(t, section, inputs)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// dict_test.go
|
||||
// t_dict_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
@@ -24,10 +24,13 @@ func TestDictParser(t *testing.T) {
|
||||
/* 1 */ {`{}`, map[any]any{}, nil},
|
||||
/* 2 */ {`{123}`, nil, errors.New(`[1:6] expected ":", got "}"`)},
|
||||
/* 3 */ {`{1:"one",2:"two",3:"three"}`, map[int64]any{int64(1): "one", int64(2): "two", int64(3): "three"}, nil},
|
||||
/* 4 */ {`{1:"one",2:"two",3:"three"}.2`, "three", nil},
|
||||
/* 4 */ {`{1:"one",2:"two",3:"three"}[3]`, "three", nil},
|
||||
/* 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`)},
|
||||
}
|
||||
|
||||
succeeded := 0
|
||||
@@ -42,11 +45,11 @@ func TestDictParser(t *testing.T) {
|
||||
var gotResult any
|
||||
var gotErr error
|
||||
|
||||
ctx := NewSimpleFuncStore()
|
||||
ctx := NewSimpleStore()
|
||||
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)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// t_expr_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExpr(t *testing.T) {
|
||||
section := "Expr"
|
||||
|
||||
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=fileOpen("test-file.txt"); line=fileRead(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 */ {`
|
||||
ds={
|
||||
"init":func(end){@end=end; @current=0 but true},
|
||||
"current":func(){current},
|
||||
"next":func(){
|
||||
((next=current+1) <= end) ? [true] {@current=next but current} :: {nil}
|
||||
}
|
||||
};
|
||||
it=$(ds,3);
|
||||
it++;
|
||||
it++
|
||||
`, int64(1), nil},
|
||||
}
|
||||
// t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// parserTestSpec(t, section, inputs, 3)
|
||||
parserTest(t, section, inputs)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// t_fractions_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFractionsParser(t *testing.T) {
|
||||
section := "Fraction"
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`1|2`, newFraction(1, 2), nil},
|
||||
/* 2 */ {`1|2 + 1`, newFraction(3, 2), nil},
|
||||
/* 3 */ {`1|2 - 1`, newFraction(-1, 2), nil},
|
||||
/* 4 */ {`1|2 * 1`, newFraction(1, 2), nil},
|
||||
/* 5 */ {`1|2 * 2|3`, newFraction(2, 6), nil},
|
||||
/* 6 */ {`1|2 / 2|3`, newFraction(3, 4), nil},
|
||||
/* 7 */ {`1|"5"`, nil, errors.New(`denominator must be integer, got string (5)`)},
|
||||
/* 8 */ {`"1"|5`, nil, errors.New(`numerator must be integer, got string (1)`)},
|
||||
/* 9 */ {`1|+5`, nil, errors.New(`[1:3] infix operator "|" requires two non-nil operands, got 1`)},
|
||||
/* 10 */ {`1|(-2)`, newFraction(-1, 2), nil},
|
||||
/* 11 */ {`builtin "math.arith"; add(1|2, 2|3)`, newFraction(7, 6), nil},
|
||||
/* 12 */ {`builtin "math.arith"; add(1|2, 1.0, 2)`, float64(3.5), nil},
|
||||
/* 13 */ {`builtin "math.arith"; mul(1|2, 2|3)`, newFraction(2, 6), nil},
|
||||
/* 14 */ {`builtin "math.arith"; mul(1|2, 1.0, 2)`, float64(1.0), nil},
|
||||
/* 15 */ {`1|0`, nil, errors.New(`division by zero`)},
|
||||
/* 16 */ {`fract(-0.5)`, newFraction(-1, 2), nil},
|
||||
/* 17 */ {`fract("")`, (*FractionType)(nil), errors.New(`bad syntax`)},
|
||||
/* 18 */ {`fract("-1")`, newFraction(-1, 1), nil},
|
||||
/* 19 */ {`fract("+1")`, newFraction(1, 1), nil},
|
||||
/* 20 */ {`fract("1a")`, (*FractionType)(nil), errors.New(`strconv.ParseInt: parsing "1a": invalid syntax`)},
|
||||
/* 21 */ {`fract(1,0)`, nil, errors.New(`fract(): division by zero`)},
|
||||
}
|
||||
parserTest(t, section, inputs)
|
||||
}
|
||||
|
||||
func TestFractionToStringSimple(t *testing.T) {
|
||||
source := newFraction(1, 2)
|
||||
want := "1|2"
|
||||
got := source.ToString(0)
|
||||
if got != want {
|
||||
t.Errorf(`(1,2) -> result = %v [%T], want = %v [%T]`, got, got, want, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFractionToStringMultiline(t *testing.T) {
|
||||
source := newFraction(1, 2)
|
||||
want := "1\n-\n2"
|
||||
got := source.ToString(MultiLine)
|
||||
if got != want {
|
||||
t.Errorf(`(1,2) -> result = %v [%T], want = %v [%T]`, got, got, want, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Check this test: the output string ends with a space
|
||||
func _TestToStringMultilineTty(t *testing.T) {
|
||||
source := newFraction(-1, 2)
|
||||
want := "\x1b[4m-1\x1b[0m\n2"
|
||||
got := source.ToString(MultiLine | TTY)
|
||||
if got != want {
|
||||
t.Errorf(`(1,2) -> result = %#v [%T], want = %#v [%T]`, got, got, want, want)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// t_funcs_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFuncs(t *testing.T) {
|
||||
section := "Funcs"
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`two=func(){2}; two()`, int64(2), nil},
|
||||
/* 2 */ {`double=func(x) {2*x}; (double(3))`, int64(6), nil},
|
||||
/* 3 */ {`double=func(x){2*x}; double(3)`, int64(6), nil},
|
||||
/* 4 */ {`double=func(x){2*x}; a=5; double(3+a) + 1`, int64(17), nil},
|
||||
/* 5 */ {`double=func(x){2*x}; a=5; two=func() {2}; (double(3+a) + 1) * two()`, int64(34), nil},
|
||||
/* 6 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
|
||||
/* 7 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
|
||||
/* 8 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
|
||||
/* 9 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable or function "x"`)},
|
||||
/* 10 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
||||
/* 11 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
||||
/* 12 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
|
||||
/* 13 */ {`two=func(){2}; four=func(f){f()+f()}; four(two)`, int64(4), nil},
|
||||
/* 14 */ {`two=func(){2}; two(123)`, nil, errors.New(`two(): too much params -- expected 0, got 1`)},
|
||||
/* 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 */ {`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)
|
||||
}
|
||||
|
||||
func dummy(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func TestFunctionToStringSimple(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFunctionGetFunc(t *testing.T) {
|
||||
source := NewGolangFunctor(dummy)
|
||||
want := ExprFunc(nil)
|
||||
got := source.GetFunc()
|
||||
if got != want {
|
||||
t.Errorf(`(func() -> result = %v [%T], want = %v [%T]`, got, got, want, want)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// graph_test.go
|
||||
// t_graph_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// helpers_test.go
|
||||
// t_helpers_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
@@ -52,7 +52,7 @@ func TestEvalStringA(t *testing.T) {
|
||||
|
||||
func TestEvalString(t *testing.T) {
|
||||
|
||||
ctx := NewSimpleVarStore()
|
||||
ctx := NewSimpleStore()
|
||||
ctx.SetVar("a", uint8(1))
|
||||
ctx.SetVar("b", int8(2))
|
||||
ctx.SetVar("f", 2.0)
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// t_index_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCollections(t *testing.T) {
|
||||
section := "Collection"
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`"abcdef"[1:3]`, "bc", nil},
|
||||
/* 2 */ {`"abcdef"[:3]`, "abc", nil},
|
||||
/* 3 */ {`"abcdef"[1:]`, "bcdef", nil},
|
||||
/* 4 */ {`"abcdef"[:]`, "abcdef", nil},
|
||||
// /* 5 */ {`[0,1,2,3,4][:]`, ListType{int64(0), int64(1), int64(2), int64(3), int64(4)}, nil},
|
||||
/* 5 */ {`"abcdef"[1:2:3]`, nil, errors.New(`[1:14] left operand '(1, 2)' [pair] and right operand '3' [integer] are not compatible with operator ":"`)},
|
||||
}
|
||||
|
||||
t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// parserTestSpec(t, section, inputs, 5)
|
||||
parserTest(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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// t_iterator_test.go
|
||||
package expr
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestIteratorParser(t *testing.T) {
|
||||
inputs := []inputType{
|
||||
/* 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,"int.list"); mul(it)`, int64(12000), nil},
|
||||
/* 8 */ {`include "test-resources/file-reader.expr"; it=$(ds,"int.list"); it++; it.index`, int64(0), nil},
|
||||
/* 10 */ {`include "test-resources/file-reader.expr"; it=$(ds,"int.list"); it.clean`, true, nil},
|
||||
/* 11 */ {`it=$(1,2,3); it++`, int64(1), nil},
|
||||
}
|
||||
// inputs1 := []inputType{
|
||||
// /* 1 */ {`0?{}`, nil, nil},
|
||||
// }
|
||||
parserTest(t, "Iterator", inputs)
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// t_list_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestListParser(t *testing.T) {
|
||||
section := "List"
|
||||
|
||||
inputs := []inputType{
|
||||
/* 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`, 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},
|
||||
/* 17 */ {`list=["one","two","three"]; list[10]`, nil, errors.New(`[1:34] index 10 out of bounds`)},
|
||||
/* 18 */ {`["a", "b", "c"]`, newListA("a", "b", "c"), nil},
|
||||
/* 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`, 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][:]`, 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`, 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`)},
|
||||
// /* 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},
|
||||
}
|
||||
|
||||
// t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// parserTestSpec(t, section, inputs, 17)
|
||||
parserTest(t, section, inputs)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// t_module-register_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIterateModules(t *testing.T) {
|
||||
section := "Module-Register"
|
||||
|
||||
mods := make([]string, 0, 100)
|
||||
IterateBuiltinModules(func(name, description string, imported bool) bool {
|
||||
mods = append(mods, name)
|
||||
return true
|
||||
})
|
||||
|
||||
t.Logf("%s -- IterateModules(): %v", section, mods)
|
||||
if len(mods) == 0 {
|
||||
t.Errorf("Module-List: got-length zero, expected-length greater than zero")
|
||||
}
|
||||
|
||||
// IterateModules(func(name, description string, imported bool) bool {
|
||||
// return false
|
||||
// })
|
||||
// if len(mods) > 0 {
|
||||
// t.Errorf("Module-List: got-length greater than zero, expected-length zero")
|
||||
// }
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// parser_test.go
|
||||
// t_parser_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
@@ -18,6 +18,8 @@ type inputType struct {
|
||||
}
|
||||
|
||||
func TestGeneralParser(t *testing.T) {
|
||||
section := "Parser"
|
||||
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`1+/*5*/2`, int64(3), nil},
|
||||
/* 2 */ {`3 == 4`, false, nil},
|
||||
@@ -115,8 +117,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},
|
||||
@@ -138,32 +140,30 @@ func TestGeneralParser(t *testing.T) {
|
||||
/* 117 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)},
|
||||
/* 118 */ {`{"key":}`, nil, errors.New(`[1:9] expected "dictionary-value", got "}"`)},
|
||||
/* 119 */ {`{}`, &DictType{}, nil},
|
||||
/* 120 */ {`1|2`, newFraction(1, 2), nil},
|
||||
/* 121 */ {`1|2 + 1`, newFraction(3, 2), nil},
|
||||
/* 122 */ {`1|2 - 1`, newFraction(-1, 2), nil},
|
||||
/* 123 */ {`1|2 * 1`, newFraction(1, 2), nil},
|
||||
/* 124 */ {`1|2 * 2|3`, newFraction(2, 6), nil},
|
||||
/* 125 */ {`1|2 / 2|3`, newFraction(3, 4), nil},
|
||||
/* 126 */ {`builtin "math.arith"; add(1,2,3)`, int64(6), nil},
|
||||
/* 127 */ {`builtin "math.arith"; mulX(1,2,3)`, nil, errors.New(`unknown function mulX()`)},
|
||||
/* 128 */ {`builtin "math.arith"; add(1+4,3+2,5*(3-2))`, int64(15), nil},
|
||||
/* 129 */ {`builtin "math.arith"; add(add(1+4),3+2,5*(3-2))`, int64(15), nil},
|
||||
/* 130 */ {`builtin "math.arith"; add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil},
|
||||
/* 131 */ {`builtin "math.arith"; a=5; b=2; add(a, b*3)`, int64(11), nil},
|
||||
/* 132 */ {`builtin "math.arith"; var2="abc"; add(1,2) but var2`, "abc", nil},
|
||||
/* 133 */ {`builtin "math.arith"; add(1|2, 2|3)`, newFraction(7, 6), nil},
|
||||
/* 134 */ {`builtin "math.arith"; add(1|2, 1.0, 2)`, float64(3.5), nil},
|
||||
/* 135 */ {`builtin "math.arith"; mul(1|2, 2|3)`, newFraction(2, 6), nil},
|
||||
/* 136 */ {`builtin "math.arith"; mul(1|2, 1.0, 2)`, float64(1.0), nil},
|
||||
/* 137 */ {`builtin "os.file"`, int64(1), nil},
|
||||
/* 138 */ {`v=10; v++; v`, int64(11), nil},
|
||||
/* 139 */ {`1+1|2+0.5`, float64(2), nil},
|
||||
/* 140 */ {`1.2()`, newFraction(6, 5), nil},
|
||||
/* 141 */ {`1|(2-2)`, nil, errors.New(`division by zero`)},
|
||||
/* 120 */ {`v=10; v++; v`, int64(11), nil},
|
||||
/* 121 */ {`1+1|2+0.5`, float64(2), nil},
|
||||
/* 122 */ {`1.2()`, newFraction(6, 5), nil},
|
||||
/* 123 */ {`1|(2-2)`, nil, errors.New(`division by zero`)},
|
||||
}
|
||||
|
||||
// t.Setenv("EXPR_PATH", ".")
|
||||
parserTest(t, "General", inputs)
|
||||
// parserTestSpec(t, section, inputs, 102)
|
||||
parserTest(t, section, 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) {
|
||||
@@ -172,37 +172,7 @@ func parserTest(t *testing.T, section string, inputs []inputType) {
|
||||
failed := 0
|
||||
|
||||
for i, input := range inputs {
|
||||
var expr Expr
|
||||
var gotResult any
|
||||
var gotErr error
|
||||
|
||||
ctx := NewSimpleFuncStore()
|
||||
parser := NewParser(ctx)
|
||||
|
||||
logTest(t, i+1, 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]", 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
|
||||
}
|
||||
}
|
||||
|
||||
good := doTest(t, section, &input, i+1)
|
||||
if good {
|
||||
succeeded++
|
||||
} else {
|
||||
@@ -212,6 +182,40 @@ func parserTest(t *testing.T, section string, inputs []inputType) {
|
||||
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()
|
||||
|
||||
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)
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// t_template_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRelational(t *testing.T) {
|
||||
section := "Relational"
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`1 == 3-2`, true, nil},
|
||||
/* 2 */ {`"a" == "a"`, true, nil},
|
||||
/* 3 */ {`true == false`, false, nil},
|
||||
/* 4 */ {`1.0 == 3.0-2`, true, nil},
|
||||
/* 5 */ {`[1,2] == [2,1]`, false, nil},
|
||||
/* 6 */ {`1 != 3-2`, false, nil},
|
||||
/* 7 */ {`"a" != "a"`, false, nil},
|
||||
/* 8 */ {`true != false`, true, nil},
|
||||
/* 9 */ {`1.0 != 3.0-2`, false, nil},
|
||||
/* 10 */ {`[1,2] != [2,1]`, true, nil},
|
||||
/* 11 */ {`1|2 == 1|3`, false, nil},
|
||||
/* 12 */ {`1|2 != 1|3`, true, nil},
|
||||
/* 13 */ {`1|2 == 4|8`, true, nil},
|
||||
/* 14 */ {`1 < 8`, true, nil},
|
||||
/* 15 */ {`1 <= 8`, true, nil},
|
||||
/* 16 */ {`"a" < "b"`, true, nil},
|
||||
/* 17 */ {`"a" <= "b"`, true, nil},
|
||||
/* 18 */ {`1.0 < 8`, true, nil},
|
||||
/* 19 */ {`1.0 <= 8`, true, nil},
|
||||
/* 20 */ {`1.0 <= 1.0`, true, nil},
|
||||
/* 21 */ {`1.0 == 1`, true, nil},
|
||||
/* 22 */ {`1|2 < 1|3`, false, nil},
|
||||
/* 23 */ {`1|2 <= 1|3`, false, nil},
|
||||
/* 24 */ {`1|2 > 1|3`, true, nil},
|
||||
/* 25 */ {`1|2 >= 1|3`, true, nil},
|
||||
/* 26 */ {`[1,2,3] > [2]`, true, nil},
|
||||
/* 27 */ {`[1,2,3] > [9]`, false, nil},
|
||||
/* 28 */ {`[1,2,3] >= [6]`, false, nil},
|
||||
/* 29 */ {`[1,2,3] >= [2,6]`, false, nil},
|
||||
/* 30 */ {`[1,[2,3],4] > [[3,2]]`, true, nil},
|
||||
/* 31 */ {`[1,[2,[3,4]],5] > [[[4,3],2]]`, true, nil},
|
||||
/* 32 */ {`[[4,3],2] IN [1,[2,[3,4]],5]`, true, nil},
|
||||
}
|
||||
|
||||
// t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// parserTestSpec(t, section, inputs, 31)
|
||||
parserTest(t, section, inputs)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// scanner_test.go
|
||||
// t_scanner_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
@@ -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,7 +1,7 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// strings_test.go
|
||||
// t_strings_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
@@ -14,8 +14,8 @@ func TestStringsParser(t *testing.T) {
|
||||
/* 2 */ {`"uno" + 2`, `uno2`, nil},
|
||||
/* 3 */ {`"uno" + (2+1)`, `uno3`, nil},
|
||||
/* 4 */ {`"uno" * (2+1)`, `unounouno`, nil},
|
||||
/* 5 */ {`"abc".1`, `b`, nil},
|
||||
/* 5 */ {`#"abc"`, int64(3), nil},
|
||||
/* 5 */ {`"abc"[1]`, `b`, nil},
|
||||
/* 6 */ {`#"abc"`, int64(3), nil},
|
||||
}
|
||||
parserTest(t, "String", inputs)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// t_template_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSomething(t *testing.T) {
|
||||
section := "Something"
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`1`, int64(1), nil},
|
||||
}
|
||||
|
||||
// t.Setenv("EXPR_PATH", ".")
|
||||
|
||||
// parserTestSpec(t, section, inputs, 1)
|
||||
parserTest(t, section, inputs)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// term_test.go
|
||||
// t_term_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// token_test.go
|
||||
// t_token_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// utils_test.go
|
||||
// t_utils_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
@@ -55,7 +55,15 @@ func TestIsString(t *testing.T) {
|
||||
}
|
||||
|
||||
count++
|
||||
b, ok := toBool(true)
|
||||
if isIterator("fake") {
|
||||
t.Errorf(`%d: isIterator("fake") -> result = true, want false`, count)
|
||||
failed++
|
||||
} else {
|
||||
succeeded++
|
||||
}
|
||||
|
||||
count++
|
||||
b, ok := ToBool(true)
|
||||
if !(ok && b) {
|
||||
t.Errorf("%d: toBool(true) b=%v, ok=%v -> result = false, want true", count, b, ok)
|
||||
failed++
|
||||
@@ -64,7 +72,7 @@ func TestIsString(t *testing.T) {
|
||||
}
|
||||
|
||||
count++
|
||||
b, ok = toBool("abc")
|
||||
b, ok = ToBool("abc")
|
||||
if !(ok && b) {
|
||||
t.Errorf("%d: toBool(\"abc\") b=%v, ok=%v -> result = false, want true", count, b, ok)
|
||||
failed++
|
||||
@@ -72,6 +80,15 @@ func TestIsString(t *testing.T) {
|
||||
succeeded++
|
||||
}
|
||||
|
||||
count++
|
||||
b, ok = ToBool([]int{})
|
||||
if ok {
|
||||
t.Errorf("%d: toBool([]) b=%v, ok=%v -> result = true, want false", count, b, ok)
|
||||
failed++
|
||||
} else {
|
||||
succeeded++
|
||||
}
|
||||
|
||||
t.Logf("test count: %d, succeeded count: %d, failed count: %d", count, succeeded, failed)
|
||||
}
|
||||
|
||||
@@ -80,7 +97,7 @@ func TestToIntOk(t *testing.T) {
|
||||
wantValue := int(64)
|
||||
wantErr := error(nil)
|
||||
|
||||
gotValue, gotErr := toInt(source, "test")
|
||||
gotValue, gotErr := ToInt(source, "test")
|
||||
|
||||
if gotErr != nil || gotValue != wantValue {
|
||||
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
|
||||
@@ -93,7 +110,7 @@ func TestToIntErr(t *testing.T) {
|
||||
wantValue := 0
|
||||
wantErr := errors.New(`test expected integer, got uint64 (64)`)
|
||||
|
||||
gotValue, gotErr := toInt(source, "test")
|
||||
gotValue, gotErr := ToInt(source, "test")
|
||||
|
||||
if gotErr.Error() != wantErr.Error() || gotValue != wantValue {
|
||||
t.Errorf("toInt(%v, \"test\") gotValue=%v, gotErr=%v -> wantValue=%v, wantErr=%v",
|
||||
@@ -12,6 +12,7 @@ type termPriority uint32
|
||||
|
||||
const (
|
||||
priNone termPriority = iota
|
||||
priRange
|
||||
priBut
|
||||
priAssign
|
||||
priOr
|
||||
@@ -155,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",
|
||||
@@ -174,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) {
|
||||
@@ -228,3 +229,12 @@ func (self *term) evalPrefix(ctx ExprContext) (childValue any, err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE Temporary solution to support function parameters with default value
|
||||
|
||||
func (self *term) forceChild(c *term) {
|
||||
if self.children == nil {
|
||||
self.children = make([]*term, 0, 1)
|
||||
}
|
||||
self.children = append(self.children, c)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
builtin ["os.file", "base"];
|
||||
|
||||
readInt=func(fh){
|
||||
line=readFile(fh);
|
||||
line=fileRead(fh);
|
||||
line ? [nil] {nil} :: {int(line)}
|
||||
};
|
||||
|
||||
ds={
|
||||
"init":func(filename){
|
||||
fh=openFile(filename);
|
||||
fh=fileOpen(filename);
|
||||
fh ? [nil] {nil} :: { @current=readInt(fh); @prev=@current };
|
||||
fh
|
||||
},
|
||||
@@ -20,7 +20,7 @@ ds={
|
||||
:: {@prev=current; @current=readInt(fh) but current}
|
||||
},
|
||||
"clean":func(fh){
|
||||
closeFile(fh)
|
||||
fileClose(fh)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,12 +40,12 @@ func IsDict(v any) (ok bool) {
|
||||
}
|
||||
|
||||
func IsFract(v any) (ok bool) {
|
||||
_, ok = v.(*fraction)
|
||||
_, ok = v.(*FractionType)
|
||||
return ok
|
||||
}
|
||||
|
||||
func IsRational(v any) (ok bool) {
|
||||
if _, ok = v.(*fraction); !ok {
|
||||
if _, ok = v.(*FractionType); !ok {
|
||||
_, ok = v.(int64)
|
||||
}
|
||||
return ok
|
||||
@@ -76,7 +76,7 @@ func isIterator(v any) (ok bool) {
|
||||
func numAsFloat(v any) (f float64) {
|
||||
var ok bool
|
||||
if f, ok = v.(float64); !ok {
|
||||
if fract, ok := v.(*fraction); ok {
|
||||
if fract, ok := v.(*FractionType); ok {
|
||||
f = fract.toFloat()
|
||||
} else {
|
||||
i, _ := v.(int64)
|
||||
@@ -86,7 +86,7 @@ func numAsFloat(v any) (f float64) {
|
||||
return
|
||||
}
|
||||
|
||||
func toBool(v any) (b bool, ok bool) {
|
||||
func ToBool(v any) (b bool, ok bool) {
|
||||
ok = true
|
||||
switch x := v.(type) {
|
||||
case string:
|
||||
@@ -201,11 +201,21 @@ func CloneFilteredMap[K comparable, V any](source map[K]V, filter func(key K) (a
|
||||
return CopyFilteredMap(dest, source, filter)
|
||||
}
|
||||
|
||||
func toInt(value any, description string) (i int, err error) {
|
||||
func ToInt(value any, description string) (i int, err error) {
|
||||
if valueInt64, ok := value.(int64); ok {
|
||||
i = int(valueInt64)
|
||||
} else if valueInt, ok := value.(int); ok {
|
||||
i = valueInt
|
||||
} else {
|
||||
err = fmt.Errorf("%s expected integer, got %T (%v)", description, value, value)
|
||||
err = fmt.Errorf("%s expected integer, got %s (%v)", description, TypeName(value), value)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ForAll[T, V any](ts []T, fn func(T) V) []V {
|
||||
result := make([]V, len(ts))
|
||||
for i, t := range ts {
|
||||
result[i] = fn(t)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user