Compare commits
246 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 79889cd8e1 | |||
| 234759158c | |||
| 00fda29606 | |||
| 2a2840bdf2 | |||
| 06373f5126 | |||
| e69dad5fb5 | |||
| 7459057df9 | |||
| 176969c956 | |||
| cde2efacf1 | |||
| d7a7b3218c | |||
| be3bb12f28 | |||
| 032916d4fa | |||
| f3cc0cc7ad | |||
| 905337f963 | |||
| 9a95a837f6 | |||
| 8547248ea2 | |||
| a02f998fc6 | |||
| e904003e6e | |||
| 990e04f957 | |||
| d8aed9dd7a | |||
| a73d24b171 | |||
| 3ebba83bce | |||
| 6b3bfa2a11 | |||
| 867806155e | |||
| a711333a2e | |||
| af3e946bd4 | |||
| 22a36fa630 | |||
| 6d9a379c92 | |||
| dd6404c786 | |||
| 34874ef663 | |||
| 9bc8e8ca05 | |||
| 7f367cfc49 | |||
| fe999acf2c | |||
| 2ed1a1842b | |||
| bb9493d0cc | |||
| f279bf163e | |||
| 6834d9f47b | |||
| 8051faa2bf | |||
| f30e687a79 | |||
| 2b6e46576b | |||
| dc06c03112 | |||
| e8f5d3e445 | |||
| 76ce0945f7 | |||
| 340b99bad7 | |||
| 93dac956fb | |||
| 307027d23d | |||
| 896844e772 | |||
| 9fc20077a1 | |||
| d2a4adebdd | |||
| fd8e32e12b | |||
| 1e62a51c15 | |||
| 0170baa0f5 | |||
| fe5c8e9619 | |||
| 7164e8816c | |||
| e0f3b028fc | |||
| 522b5983bd | |||
| f1e2163277 | |||
| bbdf498cf3 | |||
| f75c991ed2 | |||
| a7836143cc | |||
| ba9b9cb28f | |||
| ef1baa11a8 | |||
| cfddbd60b9 | |||
| 0760141caf | |||
| b9e780e659 | |||
| e41ddc664c | |||
| 3b641ac793 | |||
| a1a62b6794 | |||
| a18d92534f | |||
| ec0963e26f | |||
| be992131b1 | |||
| 0e3960321f | |||
| 61d34fef7d | |||
| 581f1585e6 | |||
| 531cb1c249 | |||
| d1b468f35b | |||
| ff9cf80c66 | |||
| d63b18fd76 | |||
| 019470faf1 | |||
| 302430d57d | |||
| 62ef0d699d | |||
| 866de759dd | |||
| b1d6b6de44 | |||
| 7e357eea62 | |||
| d6b4c79736 | |||
| d066344af8 | |||
| f41dba069e | |||
| 703ecf6829 | |||
| ba479a1b99 | |||
| 24e6a293b0 | |||
| 28f464c4dc | |||
| 9fb611aa20 | |||
| 56d6d06d15 | |||
| d9f7e5b1ad | |||
| 0f54e01ef3 | |||
| 63f5db00b3 | |||
| 9745a5d909 | |||
| 0bb4c96481 | |||
| 1757298eb4 | |||
| 54041552d4 | |||
| 5302907dcf | |||
| eb4b17f078 | |||
| 33d70d6d1a | |||
| 9df9ad5dd1 | |||
| 34dc828ead | |||
| 29bc2c62a3 | |||
| 8eb2d77ea3 | |||
| 53bcf90d2a | |||
| f347b15146 | |||
| 115ce26ce9 | |||
| 227944b3fb | |||
| 0d01afcc9f | |||
| 00c76b41f1 | |||
| 08e0979cdd | |||
| f04f5822ec | |||
| 80d47879e9 | |||
| 985eb3d19d | |||
| 45734ab393 | |||
| c100cf349d | |||
| 8144122d2c | |||
| 188ea354ee | |||
| 2fc6bbfe10 | |||
| 847d85605e | |||
| 9c29392389 | |||
| a7b6e6f8d2 | |||
| a16ac70e4a | |||
| ab2e3f0528 | |||
| 974835a8ef | |||
| 457a656073 | |||
| 9e63e1402e | |||
| e4ded4f746 | |||
| 4e3af837e6 | |||
| ca12722c93 | |||
| d96123ab02 | |||
| f2d6d63017 | |||
| 905b2af7fa | |||
| 9307473d08 | |||
| 10a596a4cd | |||
| 609fb21505 | |||
| 7650a4a441 | |||
| f51d6023ae | |||
| 99454227d5 | |||
| 75358e5d35 | |||
| 51b272dda8 | |||
| 7f282e5460 | |||
| cff21b40f7 | |||
| 4f432da2b9 | |||
| e4b4b4fb79 | |||
| c04678c426 | |||
| 9bba40f155 | |||
| f66cd1fdb1 | |||
| f41ea96d17 | |||
| d84e690ef3 | |||
| 4b25a07699 | |||
| 3736214c5a | |||
| 78cbb7b36f | |||
| 2c87d6bf9e | |||
| 691c213d17 | |||
| fa136cb70b | |||
| 76dd01afcd | |||
| 4283fab816 | |||
| 03d4c192c2 | |||
| e5f63c3d9d | |||
| d545a35acf | |||
| e4275e2cb6 | |||
| 1ff5770264 | |||
| ba32e2dccf | |||
| f22b5a6f0b | |||
| 7c8dbb0ac7 | |||
| e5c5920db0 | |||
| 61efdb4eef | |||
| 82ec78719d | |||
| 554ff1a9dd | |||
| 6bb891e09d | |||
| 1c4ffd7d64 | |||
| b92b19e1dd | |||
| 9967918418 | |||
| 6c14c07d66 | |||
| 9ea170e53b | |||
| a543360151 | |||
| 24a25bbf94 | |||
| d6a1607041 | |||
| 4d43ab2c2f | |||
| 9bd4a0ba23 | |||
| 2b184cf3f2 | |||
| 263e419d9a | |||
| c39970fa7e | |||
| 14bb9e942b | |||
| 9451958218 | |||
| 91fdc1926e | |||
| 9a3abdf1b6 | |||
| ac3e690f87 | |||
| f0a152a17a | |||
| ca89931ca9 | |||
| d2e8aed4f7 | |||
| 8138cd2a80 | |||
| 6786666cf4 | |||
| 35e794701a | |||
| 52ef134be6 | |||
| 624318d84e | |||
| aa1338cd51 | |||
| c9db4b84e3 | |||
| e7e9330b71 | |||
| 8eb25bbc86 | |||
| efc92d434b | |||
| 4151f3f5e2 | |||
| f028485caa | |||
| ab07405cda | |||
| 47be0c66cf | |||
| 50e7168214 | |||
| 0a9543543d | |||
| 775751c67b | |||
| 4aaffd6c44 | |||
| 924051fbcd | |||
| 5a9b6525a2 | |||
| 8c66d90532 | |||
| 4aa0113c6a | |||
| d035fa0d5e | |||
| cf73b5c98d | |||
| 8346e28340 | |||
| 9c2eca40d7 | |||
| c3198e4c79 | |||
| 99e0190b9c | |||
| d4f63a3837 | |||
| 389d48b646 | |||
| 1f7b9131fc | |||
| dce49fd2b7 | |||
| 8ee0bb5701 | |||
| b2b0bb04c5 | |||
| f3abf5e77c | |||
| 34b7799177 | |||
| 8c3c54913a | |||
| 5910345c08 | |||
| 8b4dad1381 | |||
| 6ef468408c | |||
| 3a30d890c6 | |||
| 71ab417a56 | |||
| 7cfb89c25c | |||
| 569dbfda9d | |||
| f342dfe9f3 | |||
| 5378952394 | |||
| a9d6a82011 | |||
| 43b74131fb | |||
| cb0eac54b2 | |||
| b219b55878 | |||
| 56c86f917e |
@@ -8,12 +8,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Expr interface {
|
|
||||||
Eval(ctx ExprContext) (result any, err error)
|
|
||||||
eval(ctx ExprContext, preset bool) (result any, err error)
|
|
||||||
String() string
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------- ast
|
//-------- ast
|
||||||
|
|
||||||
type ast struct {
|
type ast struct {
|
||||||
@@ -25,65 +19,69 @@ func NewAst() *ast {
|
|||||||
return &ast{}
|
return &ast{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ast) ToForest() {
|
func (expr *ast) TypeName() string {
|
||||||
if self.root != nil {
|
return "Expression"
|
||||||
if self.forest == nil {
|
|
||||||
self.forest = make([]*term, 0)
|
|
||||||
}
|
}
|
||||||
self.forest = append(self.forest, self.root)
|
|
||||||
self.root = nil
|
func (expr *ast) ToForest() {
|
||||||
|
if expr.root != nil {
|
||||||
|
if expr.forest == nil {
|
||||||
|
expr.forest = make([]*term, 0)
|
||||||
|
}
|
||||||
|
expr.forest = append(expr.forest, expr.root)
|
||||||
|
expr.root = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ast) String() string {
|
func (expr *ast) String() string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
if self.root == nil {
|
if expr.root == nil {
|
||||||
sb.WriteString("(nil)")
|
sb.WriteString("(nil)")
|
||||||
} else {
|
} else {
|
||||||
self.root.toString(&sb)
|
expr.root.toString(&sb)
|
||||||
}
|
}
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ast) addTokens(tokens ...*Token) (err error) {
|
func (expr *ast) addTokens(tokens ...*Token) (err error) {
|
||||||
for _, tk := range tokens {
|
for _, tk := range tokens {
|
||||||
if err = self.addToken(tk); err != nil {
|
if err = expr.addToken(tk); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ast) addToken(tk *Token) (err error) {
|
func (expr *ast) addToken(tk *Token) (err error) {
|
||||||
_, err = self.addToken2(tk)
|
_, err = expr.addToken2(tk)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ast) addToken2(tk *Token) (t *term, err error) {
|
func (expr *ast) addToken2(tk *Token) (t *term, err error) {
|
||||||
if t = newTerm(tk, nil); t != nil {
|
if t = newTerm(tk); t != nil {
|
||||||
err = self.addTerm(t)
|
err = expr.addTerm(t)
|
||||||
} else {
|
} else {
|
||||||
err = tk.Errorf("unexpected token %q", tk.String())
|
err = tk.Errorf("unexpected token %q", tk.String())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ast) addTerm(node *term) (err error) {
|
func (expr *ast) addTerm(node *term) (err error) {
|
||||||
if self.root == nil {
|
if expr.root == nil {
|
||||||
self.root = node
|
expr.root = node
|
||||||
} else {
|
} else {
|
||||||
self.root, err = self.insert(self.root, node)
|
expr.root, err = expr.insert(expr.root, node)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ast) insert(tree, node *term) (root *term, err error) {
|
func (expr *ast) insert(tree, node *term) (root *term, err error) {
|
||||||
if tree.getPriority() < node.getPriority() {
|
if tree.getPriority() < node.getPriority() {
|
||||||
root = tree
|
root = tree
|
||||||
if tree.isComplete() {
|
if tree.isComplete() {
|
||||||
var subRoot *term
|
var subRoot *term
|
||||||
last := tree.removeLastChild()
|
last := tree.removeLastChild()
|
||||||
if subRoot, err = self.insert(last, node); err == nil {
|
if subRoot, err = expr.insert(last, node); err == nil {
|
||||||
subRoot.setParent(tree)
|
subRoot.setParent(tree)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -98,28 +96,22 @@ func (self *ast) insert(tree, node *term) (root *term, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ast) Finish() {
|
func (expr *ast) Finish() {
|
||||||
if self.root == nil && self.forest != nil && len(self.forest) >= 1 {
|
if expr.root == nil && expr.forest != nil && len(expr.forest) >= 1 {
|
||||||
self.root = self.forest[len(self.forest)-1]
|
expr.root = expr.forest[len(expr.forest)-1]
|
||||||
self.forest = self.forest[0 : len(self.forest)-1]
|
expr.forest = expr.forest[0 : len(expr.forest)-1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ast) Eval(ctx ExprContext) (result any, err error) {
|
func (expr *ast) Eval(ctx ExprContext) (result any, err error) {
|
||||||
return self.eval(ctx, true)
|
expr.Finish()
|
||||||
}
|
|
||||||
|
|
||||||
func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
|
if expr.root != nil {
|
||||||
self.Finish()
|
// initDefaultVars(ctx)
|
||||||
|
if expr.forest != nil {
|
||||||
if self.root != nil {
|
for _, root := range expr.forest {
|
||||||
if preset {
|
|
||||||
initDefaultVars(ctx)
|
|
||||||
}
|
|
||||||
if self.forest != nil {
|
|
||||||
for _, root := range self.forest {
|
|
||||||
if result, err = root.compute(ctx); err == nil {
|
if result, err = root.compute(ctx); err == nil {
|
||||||
ctx.setVar(ControlLastResult, result)
|
ctx.UnsafeSetVar(ControlLastResult, result)
|
||||||
} else {
|
} else {
|
||||||
//err = fmt.Errorf("error in expression nr %d: %v", i+1, err)
|
//err = fmt.Errorf("error in expression nr %d: %v", i+1, err)
|
||||||
break
|
break
|
||||||
@@ -127,7 +119,9 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
result, err = self.root.compute(ctx)
|
if result, err = expr.root.compute(ctx); err == nil {
|
||||||
|
ctx.UnsafeSetVar(ControlLastResult, result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// } else {
|
// } else {
|
||||||
// err = errors.New("empty expression")
|
// err = errors.New("empty expression")
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// function.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
// ---- Linking with Expr functions
|
||||||
|
type exprFunctor struct {
|
||||||
|
baseFunctor
|
||||||
|
params []ExprFuncParam
|
||||||
|
expr Expr
|
||||||
|
defCtx ExprContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// func newExprFunctor(e Expr, params []string, ctx ExprContext) *exprFunctor {
|
||||||
|
// return &exprFunctor{expr: e, params: params, defCtx: ctx}
|
||||||
|
// }
|
||||||
|
|
||||||
|
func newExprFunctor(e Expr, params []ExprFuncParam, ctx ExprContext) *exprFunctor {
|
||||||
|
var defCtx ExprContext
|
||||||
|
if ctx != nil {
|
||||||
|
// if ctx.GetParent() != nil {
|
||||||
|
// defCtx = ctx.Clone()
|
||||||
|
// defCtx.SetParent(ctx)
|
||||||
|
// } else {
|
||||||
|
defCtx = ctx
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
return &exprFunctor{expr: e, params: params, defCtx: defCtx}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *exprFunctor) TypeName() string {
|
||||||
|
return "ExprFunctor"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *exprFunctor) GetDefinitionContext() ExprContext {
|
||||||
|
return functor.defCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
// if functor.defCtx != nil {
|
||||||
|
// ctx.Merge(functor.defCtx)
|
||||||
|
// }
|
||||||
|
|
||||||
|
for i, p := range functor.params {
|
||||||
|
if i < len(args) {
|
||||||
|
arg := args[i]
|
||||||
|
if funcArg, ok := arg.(Functor); ok {
|
||||||
|
paramSpecs := funcArg.GetParams()
|
||||||
|
ctx.RegisterFunc(p.Name(), funcArg, TypeAny, paramSpecs)
|
||||||
|
} else {
|
||||||
|
ctx.UnsafeSetVar(p.Name(), arg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.UnsafeSetVar(p.Name(), nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result, err = functor.expr.Eval(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// 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) TypeName() string {
|
||||||
|
return "GoFunctor"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *golangFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
return functor.f(ctx, name, args)
|
||||||
|
}
|
||||||
+214
@@ -0,0 +1,214 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtin-base.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isNilFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
result = args[0] == nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isIntFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
result = IsInteger(args[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFloatFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
result = IsFloat(args[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBoolFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
result = IsBool(args[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isStringFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
result = IsString(args[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFractionFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
result = IsFract(args[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRationalFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
result = IsRational(args[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isListFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
result = IsList(args[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDictionaryFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
result = IsDict(args[0])
|
||||||
|
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:
|
||||||
|
result = v
|
||||||
|
case float64:
|
||||||
|
result = int64(math.Trunc(v))
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
result = int64(1)
|
||||||
|
} else {
|
||||||
|
result = int64(0)
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
var i int
|
||||||
|
if i, err = strconv.Atoi(v); err == nil {
|
||||||
|
result = int64(i)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = ErrCantConvert(name, v, "int")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func decFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
switch v := args[0].(type) {
|
||||||
|
case int64:
|
||||||
|
result = float64(v)
|
||||||
|
case float64:
|
||||||
|
result = v
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
result = float64(1)
|
||||||
|
} else {
|
||||||
|
result = float64(0)
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
var f float64
|
||||||
|
if f, err = strconv.ParseFloat(v, 64); err == nil {
|
||||||
|
result = f
|
||||||
|
}
|
||||||
|
case *FractionType:
|
||||||
|
result = v.toFloat()
|
||||||
|
default:
|
||||||
|
err = ErrCantConvert(name, v, "float")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
switch v := args[0].(type) {
|
||||||
|
case int64:
|
||||||
|
result = strconv.FormatInt(v, 10)
|
||||||
|
case float64:
|
||||||
|
result = strconv.FormatFloat(v, 'g', -1, 64)
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
result = "true"
|
||||||
|
} else {
|
||||||
|
result = "false"
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
result = v
|
||||||
|
case *FractionType:
|
||||||
|
result = v.ToString(0)
|
||||||
|
case Formatter:
|
||||||
|
result = v.ToString(0)
|
||||||
|
case fmt.Stringer:
|
||||||
|
result = v.String()
|
||||||
|
default:
|
||||||
|
err = ErrCantConvert(name, v, "string")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func fractFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
switch v := args[0].(type) {
|
||||||
|
case int64:
|
||||||
|
var den int64 = 1
|
||||||
|
if len(args) > 1 {
|
||||||
|
var ok bool
|
||||||
|
if den, ok = args[1].(int64); !ok {
|
||||||
|
err = ErrExpectedGot(name, "integer", args[1])
|
||||||
|
} else if den == 0 {
|
||||||
|
err = ErrFuncDivisionByZero(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
result = newFraction(v, den)
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
result, err = float64ToFraction(v)
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
result = newFraction(1, 1)
|
||||||
|
} else {
|
||||||
|
result = newFraction(0, 1)
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
result, err = makeGeneratingFraction(v)
|
||||||
|
case *FractionType:
|
||||||
|
result = v
|
||||||
|
default:
|
||||||
|
err = ErrCantConvert(name, v, "float")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func ImportBuiltinsFuncs(ctx ExprContext) {
|
||||||
|
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("string", NewGolangFunctor(stringFunc), TypeString, anyParams)
|
||||||
|
ctx.RegisterFunc("fract", NewGolangFunctor(fractFunc), TypeFraction, []ExprFuncParam{
|
||||||
|
NewFuncParam(ParamValue),
|
||||||
|
NewFuncParamFlagDef("denominator", PfDefault, 1),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterBuiltinModule("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtin-fmt.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getStdout(ctx ExprContext) io.Writer {
|
||||||
|
var w io.Writer
|
||||||
|
if wany, exists := ctx.GetVar(ControlStdout); exists && wany != nil {
|
||||||
|
w, _ = wany.(io.Writer)
|
||||||
|
}
|
||||||
|
if w == nil {
|
||||||
|
w = os.Stdout
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func printFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var n int
|
||||||
|
if n, err = fmt.Fprint(getStdout(ctx), args...); err == nil {
|
||||||
|
result = int64(n)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func printLnFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var n int
|
||||||
|
if n, err = fmt.Fprintln(getStdout(ctx), args...); err == nil {
|
||||||
|
result = int64(n)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportFmtFuncs(ctx ExprContext) {
|
||||||
|
ctx.RegisterFunc("print", NewGolangFunctor(printFunc), TypeInt, []ExprFuncParam{
|
||||||
|
NewFuncParamFlag(ParamItem, PfRepeat),
|
||||||
|
})
|
||||||
|
ctx.RegisterFunc("println", NewGolangFunctor(printLnFunc), TypeInt, []ExprFuncParam{
|
||||||
|
NewFuncParamFlag(ParamItem, PfRepeat),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterBuiltinModule("fmt", ImportFmtFuncs, "String and console formatting functions")
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtin-import.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func importFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
return importGeneral(ctx, name, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func importAllFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
CtrlEnable(ctx, control_export_all)
|
||||||
|
return importGeneral(ctx, name, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func importGeneral(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
dirList := buildSearchDirList("sources", ENV_EXPR_SOURCE_PATH)
|
||||||
|
result, err = doImport(ctx, name, dirList, NewArrayIterator(args))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (result any, err error) {
|
||||||
|
var v any
|
||||||
|
var sourceFilepath string
|
||||||
|
|
||||||
|
for v, err = it.Next(); err == nil; v, err = it.Next() {
|
||||||
|
if err = checkStringParamExpected(name, v, it.Index()); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if sourceFilepath, err = makeFilepath(v.(string), dirList); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var file *os.File
|
||||||
|
if file, err = os.Open(sourceFilepath); err == nil {
|
||||||
|
defer file.Close()
|
||||||
|
var expr *ast
|
||||||
|
scanner := NewScanner(file, DefaultTranslations())
|
||||||
|
parser := NewParser()
|
||||||
|
if expr, err = parser.parseGeneral(scanner, true, true); err == nil {
|
||||||
|
result, err = expr.Eval(ctx)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
} else {
|
||||||
|
result = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportImportFuncs(ctx ExprContext) {
|
||||||
|
ctx.RegisterFunc("import", NewGolangFunctor(importFunc), TypeAny, []ExprFuncParam{
|
||||||
|
NewFuncParamFlag(ParamFilepath, PfRepeat),
|
||||||
|
})
|
||||||
|
ctx.RegisterFunc("importAll", NewGolangFunctor(importAllFunc), TypeAny, []ExprFuncParam{
|
||||||
|
NewFuncParamFlag(ParamFilepath, PfRepeat),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterBuiltinModule("import", ImportImportFuncs, "Functions import() and include()")
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtin-iterator.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
iterParamOperator = "operator"
|
||||||
|
iterParamVars = "vars"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseRunArgs(localCtx ExprContext, args []any) (it Iterator, op Functor, err error) {
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if it, ok = args[0].(Iterator); !ok {
|
||||||
|
err = fmt.Errorf("paramter %q must be an iterator, passed %v [%s]", ParamIterator, args[0], TypeName(args[0]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) > 1 {
|
||||||
|
if op, ok = args[1].(Functor); !ok || op == nil {
|
||||||
|
err = fmt.Errorf("paramter %q must be a function, passed %v [%s]", iterParamOperator, args[1], TypeName(args[1]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(args) > 2 {
|
||||||
|
var vars *DictType
|
||||||
|
if vars, ok = args[2].(*DictType); !ok || vars == nil {
|
||||||
|
err = fmt.Errorf("paramter %q must be a dictionary, passed %v [%s]", iterParamVars, args[2], TypeName(args[2]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for key, value := range *vars {
|
||||||
|
var varName string
|
||||||
|
if varName, ok = key.(string); ok {
|
||||||
|
localCtx.UnsafeSetVar(varName, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func runFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var it Iterator
|
||||||
|
var ok bool
|
||||||
|
var op Functor
|
||||||
|
var v any
|
||||||
|
var usingDefaultOp = false
|
||||||
|
var params []any
|
||||||
|
var item any
|
||||||
|
|
||||||
|
localCtx := ctx.Clone()
|
||||||
|
localCtx.UnsafeSetVar("it_status", nil)
|
||||||
|
|
||||||
|
if it, op, err = parseRunArgs(localCtx, args); err != nil {
|
||||||
|
return
|
||||||
|
} else if op == nil {
|
||||||
|
op = NewGolangFunctor(printLnFunc)
|
||||||
|
usingDefaultOp = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for item, err = it.Next(); err == nil; item, err = it.Next() {
|
||||||
|
if usingDefaultOp {
|
||||||
|
params = []any{item}
|
||||||
|
} else {
|
||||||
|
params = []any{it.Index(), item}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err = op.Invoke(localCtx, iterParamOperator, params); err != nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
var success bool
|
||||||
|
if success, ok = ToBool(v); !success || !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
result, _ = localCtx.GetVar("it_status")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportIterFuncs(ctx ExprContext) {
|
||||||
|
ctx.RegisterFunc("run", NewGolangFunctor(runFunc), TypeAny, []ExprFuncParam{
|
||||||
|
NewFuncParam(ParamIterator),
|
||||||
|
NewFuncParamFlag(iterParamOperator, PfOptional),
|
||||||
|
NewFuncParamFlag(iterParamVars, PfOptional),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterBuiltinModule("iterator", ImportIterFuncs, "Iterator helper functions")
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
// funcs-math.go
|
// builtin-math-arith.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func checkNumberParamExpected(funcName string, paramValue any, paramPos, level, subPos int) (err error) {
|
func checkNumberParamExpected(funcName string, paramValue any, paramPos, level, subPos int) (err error) {
|
||||||
if !(isNumber(paramValue) || isFraction(paramValue)) /*|| isList(paramValue)*/ {
|
if !(IsNumber(paramValue) || isFraction(paramValue)) /*|| isList(paramValue)*/ {
|
||||||
err = fmt.Errorf("%s(): param nr %d (%d in %d) has wrong type %T, number expected",
|
err = fmt.Errorf("%s(): param nr %d (%d in %d) has wrong type %T, number expected",
|
||||||
funcName, paramPos+1, subPos+1, level, paramValue)
|
funcName, paramPos+1, subPos+1, level, paramValue)
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
|
|||||||
var sumAsFloat, sumAsFract bool
|
var sumAsFloat, sumAsFract bool
|
||||||
var floatSum float64 = 0.0
|
var floatSum float64 = 0.0
|
||||||
var intSum int64 = 0
|
var intSum int64 = 0
|
||||||
var fractSum *fraction
|
var fractSum *FractionType
|
||||||
var v any
|
var v any
|
||||||
|
|
||||||
level++
|
level++
|
||||||
@@ -45,7 +45,7 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
|
|||||||
count++
|
count++
|
||||||
|
|
||||||
if !sumAsFloat {
|
if !sumAsFloat {
|
||||||
if isFloat(v) {
|
if IsFloat(v) {
|
||||||
sumAsFloat = true
|
sumAsFloat = true
|
||||||
if sumAsFract {
|
if sumAsFract {
|
||||||
floatSum = fractSum.toFloat()
|
floatSum = fractSum.toFloat()
|
||||||
@@ -61,9 +61,9 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
|
|||||||
if sumAsFloat {
|
if sumAsFloat {
|
||||||
floatSum += numAsFloat(v)
|
floatSum += numAsFloat(v)
|
||||||
} else if sumAsFract {
|
} else if sumAsFract {
|
||||||
var item *fraction
|
var item *FractionType
|
||||||
var ok bool
|
var ok bool
|
||||||
if item, ok = v.(*fraction); !ok {
|
if item, ok = v.(*FractionType); !ok {
|
||||||
iv, _ := v.(int64)
|
iv, _ := v.(int64)
|
||||||
item = newFraction(iv, 1)
|
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 mulAsFloat, mulAsFract bool
|
||||||
var floatProd float64 = 1.0
|
var floatProd float64 = 1.0
|
||||||
var intProd int64 = 1
|
var intProd int64 = 1
|
||||||
var fractProd *fraction
|
var fractProd *FractionType
|
||||||
var v any
|
var v any
|
||||||
|
|
||||||
level++
|
level++
|
||||||
@@ -120,7 +120,7 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
|
|||||||
count++
|
count++
|
||||||
|
|
||||||
if !mulAsFloat {
|
if !mulAsFloat {
|
||||||
if isFloat(v) {
|
if IsFloat(v) {
|
||||||
mulAsFloat = true
|
mulAsFloat = true
|
||||||
if mulAsFract {
|
if mulAsFract {
|
||||||
floatProd = fractProd.toFloat()
|
floatProd = fractProd.toFloat()
|
||||||
@@ -136,9 +136,9 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
|
|||||||
if mulAsFloat {
|
if mulAsFloat {
|
||||||
floatProd *= numAsFloat(v)
|
floatProd *= numAsFloat(v)
|
||||||
} else if mulAsFract {
|
} else if mulAsFract {
|
||||||
var item *fraction
|
var item *FractionType
|
||||||
var ok bool
|
var ok bool
|
||||||
if item, ok = v.(*fraction); !ok {
|
if item, ok = v.(*FractionType); !ok {
|
||||||
iv, _ := v.(int64)
|
iv, _ := v.(int64)
|
||||||
item = newFraction(iv, 1)
|
item = newFraction(iv, 1)
|
||||||
}
|
}
|
||||||
@@ -167,10 +167,15 @@ func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ImportMathFuncs(ctx ExprContext) {
|
func ImportMathFuncs(ctx ExprContext) {
|
||||||
ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
|
ctx.RegisterFunc("add", &golangFunctor{f: addFunc}, TypeNumber, []ExprFuncParam{
|
||||||
ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
|
NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, int64(0)),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("mul", &golangFunctor{f: mulFunc}, TypeNumber, []ExprFuncParam{
|
||||||
|
NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, int64(1)),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
registerImport("math.arith", ImportMathFuncs, "Functions add() and mul()")
|
RegisterBuiltinModule("math.arith", ImportMathFuncs, "Functions add() and mul()")
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
// 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(name)
|
||||||
|
}
|
||||||
|
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(name)
|
||||||
|
}
|
||||||
|
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(name)
|
||||||
|
}
|
||||||
|
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(name, handle)
|
||||||
|
}
|
||||||
|
result = err == nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileWriteTextFunc(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(name, invalidFileHandle)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileReadTextFunc(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]
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err = r.reader.ReadString(limit)
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if len(v) > 0 {
|
||||||
|
if v[len(v)-1] == limit {
|
||||||
|
result = v[0 : len(v)-1]
|
||||||
|
} else {
|
||||||
|
result = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
invalidFileHandle = handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && (handle == nil || invalidFileHandle != nil) {
|
||||||
|
err = errInvalidFileHandle(name, invalidFileHandle)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileReadTextAllFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var handle osHandle
|
||||||
|
var invalidFileHandle any
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
result = nil
|
||||||
|
if handle, ok = args[0].(osHandle); !ok || args[0] == nil {
|
||||||
|
invalidFileHandle = args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if handle != nil {
|
||||||
|
if r, ok := handle.(*osReader); ok {
|
||||||
|
var b []byte
|
||||||
|
b, err = io.ReadAll(r.reader)
|
||||||
|
result = string(b)
|
||||||
|
} else {
|
||||||
|
invalidFileHandle = handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && (handle == nil || invalidFileHandle != nil) {
|
||||||
|
err = errInvalidFileHandle(name, invalidFileHandle)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportOsFuncs(ctx ExprContext) {
|
||||||
|
ctx.RegisterFunc("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("fileClose", NewGolangFunctor(closeFileFunc), TypeBoolean, []ExprFuncParam{
|
||||||
|
NewFuncParam(TypeHandle),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("fileWriteText", NewGolangFunctor(fileWriteTextFunc), TypeInt, []ExprFuncParam{
|
||||||
|
NewFuncParam(TypeHandle),
|
||||||
|
NewFuncParamFlagDef(TypeItem, PfDefault|PfRepeat, ""),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("fileReadText", NewGolangFunctor(fileReadTextFunc), TypeString, []ExprFuncParam{
|
||||||
|
NewFuncParam(TypeHandle),
|
||||||
|
NewFuncParamFlagDef("limitCh", PfDefault, "\n"),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("fileReadTextAll", NewGolangFunctor(fileReadTextAllFunc), TypeString, []ExprFuncParam{
|
||||||
|
NewFuncParam(TypeHandle),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterBuiltinModule("os.file", ImportOsFuncs, "Operating system file functions")
|
||||||
|
}
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtin-string.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- Start of function definitions
|
||||||
|
func doJoinStr(funcName string, sep string, it Iterator) (result any, err error) {
|
||||||
|
var sb strings.Builder
|
||||||
|
var v any
|
||||||
|
for v, err = it.Next(); err == nil; v, err = it.Next() {
|
||||||
|
if it.Index() > 0 {
|
||||||
|
sb.WriteString(sep)
|
||||||
|
}
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
sb.WriteString(s)
|
||||||
|
} else {
|
||||||
|
err = ErrExpectedGot(funcName, TypeString, v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
result = sb.String()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
// if len(args) < 1 {
|
||||||
|
// return nil, errMissingRequiredParameter(name, paramSeparator)
|
||||||
|
// }
|
||||||
|
if sep, ok := args[0].(string); ok {
|
||||||
|
if len(args) == 1 {
|
||||||
|
result = ""
|
||||||
|
} else if len(args) == 2 {
|
||||||
|
if ls, ok := args[1].(*ListType); ok {
|
||||||
|
result, err = doJoinStr(name, sep, NewListIterator(ls, nil))
|
||||||
|
} else if it, ok := args[1].(Iterator); ok {
|
||||||
|
result, err = doJoinStr(name, sep, it)
|
||||||
|
} else {
|
||||||
|
err = ErrInvalidParameterValue(name, ParamParts, args[1])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result, err = doJoinStr(name, sep, NewArrayIterator(args[1:]))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = ErrWrongParamType(name, ParamSeparator, TypeString, args[0])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var start = 0
|
||||||
|
var count = -1
|
||||||
|
var source string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if source, ok = args[0].(string); !ok {
|
||||||
|
return nil, ErrWrongParamType(name, ParamSource, TypeString, args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if start, err = ToGoInt(args[1], name+"()"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if count, err = ToGoInt(args[2], name+"()"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if start < 0 {
|
||||||
|
start = len(source) + start
|
||||||
|
}
|
||||||
|
|
||||||
|
if count < 0 {
|
||||||
|
count = len(source) - start
|
||||||
|
}
|
||||||
|
end := min(start+count, len(source))
|
||||||
|
result = source[start:end]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var source string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if source, ok = args[0].(string); !ok {
|
||||||
|
return nil, ErrWrongParamType(name, ParamSource, TypeString, args[0])
|
||||||
|
}
|
||||||
|
result = strings.TrimSpace(source)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func startsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var source string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
result = false
|
||||||
|
|
||||||
|
if source, ok = args[0].(string); !ok {
|
||||||
|
return result, ErrWrongParamType(name, ParamSource, TypeString, args[0])
|
||||||
|
}
|
||||||
|
for i, targetSpec := range args[1:] {
|
||||||
|
if target, ok := targetSpec.(string); ok {
|
||||||
|
if strings.HasPrefix(source, target) {
|
||||||
|
result = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("target item nr %d is %T, expected string", i+1, targetSpec)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func endsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var source string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
result = false
|
||||||
|
|
||||||
|
if source, ok = args[0].(string); !ok {
|
||||||
|
return result, ErrWrongParamType(name, ParamSource, TypeString, args[0])
|
||||||
|
}
|
||||||
|
for i, targetSpec := range args[1:] {
|
||||||
|
if target, ok := targetSpec.(string); ok {
|
||||||
|
if strings.HasSuffix(source, target) {
|
||||||
|
result = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("target item nr %d is %T, expected string", i+1, targetSpec)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var source, sep string
|
||||||
|
var count int = -1
|
||||||
|
var parts []string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if source, ok = args[0].(string); !ok {
|
||||||
|
return result, ErrWrongParamType(name, ParamSource, TypeString, args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if sep, ok = args[1].(string); !ok {
|
||||||
|
return nil, fmt.Errorf("separator param must be string, got %T (%v)", args[1], args[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
parts = strings.Split(source, sep)
|
||||||
|
} else {
|
||||||
|
parts = []string{}
|
||||||
|
}
|
||||||
|
list := make(ListType, len(parts))
|
||||||
|
for i, part := range parts {
|
||||||
|
list[i] = part
|
||||||
|
}
|
||||||
|
result = &list
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- End of function definitions
|
||||||
|
|
||||||
|
// Import above functions in the context
|
||||||
|
func ImportStringFuncs(ctx ExprContext) {
|
||||||
|
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() {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-9
@@ -18,17 +18,17 @@ func NewByteSlider(size int) *ByteSlider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ByteSlider) PushEnd(b byte) {
|
func (slider *ByteSlider) PushEnd(b byte) {
|
||||||
if self.length == cap(self.buf) {
|
if slider.length == cap(slider.buf) {
|
||||||
self.length--
|
slider.length--
|
||||||
for i := 0; i < self.length; i++ {
|
for i := 0; i < slider.length; i++ {
|
||||||
self.buf[i] = self.buf[i+1]
|
slider.buf[i] = slider.buf[i+1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.buf[self.length] = b
|
slider.buf[slider.length] = b
|
||||||
self.length++
|
slider.length++
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *ByteSlider) Equal(target []byte) bool {
|
func (slider *ByteSlider) Equal(target []byte) bool {
|
||||||
return target != nil && bytes.Equal(self.buf, target)
|
return target != nil && bytes.Equal(slider.buf, target)
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-13
@@ -8,30 +8,54 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ErrTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error) {
|
||||||
|
if maxArgs < 0 {
|
||||||
|
err = fmt.Errorf("%s(): too few params -- expected %d or more, got %d", funcName, minArgs, argCount)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%s(): too few params -- expected %d, got %d", funcName, minArgs, argCount)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// --- General errors
|
||||||
|
|
||||||
func errCantConvert(funcName string, value any, kind string) error {
|
func ErrCantConvert(funcName string, value any, kind string) error {
|
||||||
return fmt.Errorf("%s() can't convert %T to %s", funcName, value, kind)
|
return fmt.Errorf("%s(): can't convert %s to %s", funcName, TypeName(value), kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
func errExpectedGot(funcName string, kind string, value any) error {
|
func ErrExpectedGot(funcName string, kind string, value any) error {
|
||||||
return fmt.Errorf("%s() expected %s, got %T (%v)", funcName, kind, value, value)
|
return fmt.Errorf("%s(): expected %s, got %s (%v)", funcName, kind, TypeName(value), value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ErrFuncDivisionByZero(funcName string) error {
|
||||||
|
return fmt.Errorf("%s(): division by zero", funcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// func ErrDivisionByZero() error {
|
||||||
|
// return fmt.Errorf("division by zero")
|
||||||
|
// }
|
||||||
|
|
||||||
// --- Parameter errors
|
// --- Parameter errors
|
||||||
|
|
||||||
func errOneParam(funcName string) error {
|
// func ErrMissingRequiredParameter(funcName, paramName string) error {
|
||||||
return fmt.Errorf("%s() requires exactly one param", funcName)
|
// return fmt.Errorf("%s(): missing required parameter %q", funcName, paramName)
|
||||||
|
// }
|
||||||
|
|
||||||
|
func ErrInvalidParameterValue(funcName, paramName string, paramValue any) error {
|
||||||
|
return fmt.Errorf("%s(): invalid value %s (%v) for parameter %q", funcName, TypeName(paramValue), paramValue, paramName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func errMissingRequiredParameter(funcName, paramName string) error {
|
func ErrWrongParamType(funcName, paramName, paramType string, paramValue any) error {
|
||||||
return fmt.Errorf("%s() missing required parameter %q", funcName, paramName)
|
return fmt.Errorf("%s(): the %q parameter must be a %s, got a %s (%v)", funcName, paramName, paramType, TypeName(paramValue), paramValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func errInvalidParameterValue(funcName, paramName string, paramValue any) error {
|
// --- Operator errors
|
||||||
return fmt.Errorf("%s() invalid value %T (%v) for parameter %q", funcName, paramValue, paramValue, paramName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func errWrongParamType(funcName, paramName, paramType string, paramValue any) error {
|
func ErrLeftOperandMustBeVariable(leftTerm, opTerm *term) error {
|
||||||
return fmt.Errorf("%s() the %q parameter must be a %s, got a %T (%v)", funcName, paramName, paramType, paramValue, paramValue)
|
return leftTerm.Errorf("left operand of %q must be a variable", opTerm.source())
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-3
@@ -5,7 +5,24 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
const (
|
const (
|
||||||
paramParts = "parts"
|
ParamCount = "count"
|
||||||
paramSeparator = "separator"
|
ParamItem = "item"
|
||||||
paramSource = "source"
|
ParamParts = "parts"
|
||||||
|
ParamSeparator = "separator"
|
||||||
|
ParamSource = "source"
|
||||||
|
ParamSuffix = "suffix"
|
||||||
|
ParamPrefix = "prefix"
|
||||||
|
ParamStart = "start"
|
||||||
|
ParamEnd = "end"
|
||||||
|
ParamValue = "value"
|
||||||
|
ParamName = "name"
|
||||||
|
ParamEllipsis = "..."
|
||||||
|
ParamFilepath = "filepath"
|
||||||
|
ParamDirpath = "dirpath"
|
||||||
|
ParamIterator = "iterator"
|
||||||
|
)
|
||||||
|
|
||||||
|
// to be moved in its own source file
|
||||||
|
const (
|
||||||
|
ConstLastIndex = 0xFFFF_FFFF
|
||||||
)
|
)
|
||||||
|
|||||||
+12
-6
@@ -5,10 +5,16 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
const (
|
const (
|
||||||
typeBoolean = "boolean"
|
TypeAny = "any"
|
||||||
typeFloat = "decimal"
|
TypeBoolean = "boolean"
|
||||||
typeFraction = "fraction"
|
TypeFloat = "float"
|
||||||
typeInt = "integer"
|
TypeFraction = "fraction"
|
||||||
typeNumber = "number"
|
TypeHandle = "handle"
|
||||||
typeString = "string"
|
TypeInt = "integer"
|
||||||
|
TypeItem = "item"
|
||||||
|
TypeNumber = "number"
|
||||||
|
TypePair = "pair"
|
||||||
|
TypeString = "string"
|
||||||
|
TypeListOf = "list-of-"
|
||||||
|
TypeListOfStrings = "list-of-strings"
|
||||||
)
|
)
|
||||||
|
|||||||
+9
-3
@@ -15,18 +15,18 @@ func exportVar(ctx ExprContext, name string, value any) {
|
|||||||
if name[0] == '@' {
|
if name[0] == '@' {
|
||||||
name = name[1:]
|
name = name[1:]
|
||||||
}
|
}
|
||||||
ctx.setVar(name, value)
|
ctx.UnsafeSetVar(name, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
|
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
|
||||||
if name[0] == '@' {
|
if name[0] == '@' {
|
||||||
name = name[1:]
|
name = name[1:]
|
||||||
}
|
}
|
||||||
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
|
ctx.RegisterFunc(name, info.Functor(), info.ReturnType(), info.Params())
|
||||||
}
|
}
|
||||||
|
|
||||||
func exportObjects(destCtx, sourceCtx ExprContext) {
|
func exportObjects(destCtx, sourceCtx ExprContext) {
|
||||||
exportAll := isEnabled(sourceCtx, control_export_all)
|
exportAll := CtrlIsEnabled(sourceCtx, control_export_all)
|
||||||
// fmt.Printf("Exporting from sourceCtx [%p] to destCtx [%p] -- exportAll=%t\n", sourceCtx, destCtx, exportAll)
|
// fmt.Printf("Exporting from sourceCtx [%p] to destCtx [%p] -- exportAll=%t\n", sourceCtx, destCtx, exportAll)
|
||||||
// Export variables
|
// Export variables
|
||||||
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
|
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
|
||||||
@@ -41,3 +41,9 @@ func exportObjects(destCtx, sourceCtx ExprContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func exportObjectsToParent(sourceCtx ExprContext) {
|
||||||
|
if parentCtx := sourceCtx.GetParent(); parentCtx != nil {
|
||||||
|
exportObjects(parentCtx, sourceCtx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
-42
@@ -1,42 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 Info
|
|
||||||
type ExprFunc interface {
|
|
||||||
Name() string
|
|
||||||
MinArgs() int
|
|
||||||
MaxArgs() int
|
|
||||||
Functor() Functor
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----Expression Context
|
|
||||||
type ExprContext interface {
|
|
||||||
Clone() ExprContext
|
|
||||||
GetVar(varName string) (value any, exists bool)
|
|
||||||
SetVar(varName string, value any)
|
|
||||||
setVar(varName string, value any)
|
|
||||||
EnumVars(func(name string) (accept bool)) (varNames []string)
|
|
||||||
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
|
|
||||||
GetFuncInfo(name string) (item ExprFunc, exists bool)
|
|
||||||
Call(name string, args []any) (result any, err error)
|
|
||||||
RegisterFunc(name string, f Functor, minArgs, maxArgs int)
|
|
||||||
}
|
|
||||||
+15
-37
@@ -4,13 +4,14 @@
|
|||||||
// control.go
|
// control.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
// Preset control variables
|
// Preset control variables
|
||||||
const (
|
const (
|
||||||
|
ControlPreset = "_preset"
|
||||||
ControlLastResult = "last"
|
ControlLastResult = "last"
|
||||||
ControlBoolShortcut = "_bool_shortcut"
|
ControlBoolShortcut = "_bool_shortcut"
|
||||||
ControlImportPath = "_import_path"
|
ControlSearchPath = "_search_path"
|
||||||
|
ControlParentContext = "_parent_context"
|
||||||
|
ControlStdout = "_stdout"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Other control variables
|
// Other control variables
|
||||||
@@ -20,43 +21,20 @@ const (
|
|||||||
|
|
||||||
// Initial values
|
// Initial values
|
||||||
const (
|
const (
|
||||||
init_import_path = "~/.local/lib/go-pkg/expr:/usr/local/lib/go-pkg/expr:/usr/lib/go-pkg/expr"
|
init_search_path = "~/.local/lib/go-pkg/expr:/usr/local/lib/go-pkg/expr:/usr/lib/go-pkg/expr"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func SetCtrl(ctx ExprContext, name string, value any) (current any) {
|
||||||
|
current, _ = ctx.GetVar(name)
|
||||||
|
ctx.UnsafeSetVar(name, value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func initDefaultVars(ctx ExprContext) {
|
func initDefaultVars(ctx ExprContext) {
|
||||||
ctx.SetVar(ControlBoolShortcut, true)
|
if _, exists := ctx.GetVar(ControlPreset); exists {
|
||||||
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
|
return
|
||||||
}
|
}
|
||||||
|
ctx.UnsafeSetVar(ControlPreset, true)
|
||||||
func getControlString(ctx ExprContext, name string) (s string, exists bool) {
|
ctx.UnsafeSetVar(ControlBoolShortcut, true)
|
||||||
var v any
|
ctx.UnsafeSetVar(ControlSearchPath, init_search_path)
|
||||||
if v, exists = ctx.GetVar(name); exists {
|
|
||||||
s, exists = v.(string)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|||||||
+66
-3
@@ -29,6 +29,10 @@ func newDataCursor(ctx ExprContext, ds map[string]Functor) (dc *dataCursor) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) TypeName() string {
|
||||||
|
return "DataCursor"
|
||||||
|
}
|
||||||
|
|
||||||
// func mapToString(m map[string]Functor) string {
|
// func mapToString(m map[string]Functor) string {
|
||||||
// var sb strings.Builder
|
// var sb strings.Builder
|
||||||
// sb.WriteByte('{')
|
// sb.WriteByte('{')
|
||||||
@@ -129,20 +133,79 @@ func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func (dc *dataCursor) _Next() (item any, err error) { // must return io.EOF after the last item
|
||||||
|
// if dc.resource != nil {
|
||||||
|
// ctx := cloneContext(dc.ctx)
|
||||||
|
// // fmt.Printf("Entering Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
|
||||||
|
// if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil {
|
||||||
|
// if item == nil {
|
||||||
|
// err = io.EOF
|
||||||
|
// } else {
|
||||||
|
// dc.index++
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// // fmt.Printf("Exiting Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
|
||||||
|
// exportObjects(dc.ctx, ctx)
|
||||||
|
// // fmt.Printf("Outer-Ctx [%p]: %s\n", dc.ctx, CtxToString(dc.ctx, 0))
|
||||||
|
// } else {
|
||||||
|
// err = errInvalidDataSource()
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (dc *dataCursor) _filter(item any) (filterdItem any, err error) {
|
||||||
|
// if filter, ok := dc.ds[filterName]; ok {
|
||||||
|
// ctx := cloneContext(dc.ctx)
|
||||||
|
// filterdItem, err = filter.Invoke(ctx, filterName, []any{item, dc.index})
|
||||||
|
// } else {
|
||||||
|
// filterdItem = item
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (dc *dataCursor) checkFilter(filter Functor, item any) (accepted bool, err error) {
|
||||||
|
var v any
|
||||||
|
var ok bool
|
||||||
|
ctx := cloneContext(dc.ctx)
|
||||||
|
if v, err = filter.Invoke(ctx, filterName, []any{item, dc.index}); err == nil && v != nil {
|
||||||
|
if accepted, ok = v.(bool); !ok {
|
||||||
|
accepted = true // NOTE: A non-boolean value that is not nil means the item has been accepted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
func (dc *dataCursor) mapItem(mapper Functor, item any) (mappedItem any, err error) {
|
||||||
|
ctx := cloneContext(dc.ctx)
|
||||||
|
mappedItem, err = mapper.Invoke(ctx, mapName, []any{item, dc.index});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item
|
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item
|
||||||
|
var accepted bool
|
||||||
if dc.resource != nil {
|
if dc.resource != nil {
|
||||||
|
filter := dc.ds[filterName]
|
||||||
|
mapper := dc.ds[mapName]
|
||||||
|
|
||||||
|
for item == nil && err == nil {
|
||||||
ctx := cloneContext(dc.ctx)
|
ctx := cloneContext(dc.ctx)
|
||||||
// fmt.Printf("Entering Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
|
|
||||||
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil {
|
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil {
|
||||||
if item == nil {
|
if item == nil {
|
||||||
err = io.EOF
|
err = io.EOF
|
||||||
} else {
|
} else {
|
||||||
dc.index++
|
dc.index++
|
||||||
|
if filter != nil {
|
||||||
|
if accepted, err = dc.checkFilter(filter, item); err != nil || !accepted {
|
||||||
|
item = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if item != nil && mapper != nil {
|
||||||
|
item, err = dc.mapItem(mapper, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// fmt.Printf("Exiting Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
|
|
||||||
exportObjects(dc.ctx, ctx)
|
exportObjects(dc.ctx, ctx)
|
||||||
// fmt.Printf("Outer-Ctx [%p]: %s\n", dc.ctx, CtxToString(dc.ctx, 0))
|
}
|
||||||
} else {
|
} else {
|
||||||
err = errInvalidDataSource()
|
err = errInvalidDataSource()
|
||||||
}
|
}
|
||||||
|
|||||||
+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
|
||||||
|
}
|
||||||
+641
-146
@@ -9,6 +9,7 @@ Expressions calculator
|
|||||||
:icons: font
|
:icons: font
|
||||||
:icon-set: fi
|
:icon-set: fi
|
||||||
:numbered:
|
:numbered:
|
||||||
|
:data-uri:
|
||||||
//:table-caption: Tabella
|
//:table-caption: Tabella
|
||||||
//:figure-caption: Diagramma
|
//:figure-caption: Diagramma
|
||||||
:docinfo1:
|
:docinfo1:
|
||||||
@@ -19,52 +20,293 @@ Expressions calculator
|
|||||||
:rouge-style: gruvbox
|
:rouge-style: gruvbox
|
||||||
// :rouge-style: colorful
|
// :rouge-style: colorful
|
||||||
//:rouge-style: monokay
|
//:rouge-style: monokay
|
||||||
|
// Work around to manage double-column in back-tick quotes
|
||||||
|
:2c: ::
|
||||||
|
|
||||||
toc::[]
|
toc::[]
|
||||||
|
|
||||||
#TODO: Work in progress#
|
#TODO: Work in progress (last update on 2024/06/21, 05:40 a.m.)#
|
||||||
|
|
||||||
== Expr
|
== Expr
|
||||||
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
|
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
|
||||||
|
|
||||||
|
|
||||||
=== Concepts and terminology
|
=== Concepts and terminology
|
||||||
#TODO#
|
|
||||||
|
Expressions are texts containing sequences of operations represented by a syntax very similar to that of most programming languages. _Expr_ package provides these macro functions:
|
||||||
|
|
||||||
|
* *_Scanner_* -- Its input is a text. It scans expression text characters to produce a flow of logical symbol and related attributes, aka tokens.
|
||||||
|
* *_Parser_* -- Parser input is the token flow coming from the scanner. It analyses the token flow verifyng if it complies with the _Expr_ syntax. If that is the case, the Parser generates the Abstract Syntax Tree (AST). This is tree data structure that represents the components of an expressions and how they are related one each other.
|
||||||
|
* *_Calculator_*. Its input is the AST. It computes the parsed expression contained in the AST and returns the result or an error.
|
||||||
|
|
||||||
|
image::expression-diagram.png[]
|
||||||
|
|
||||||
|
==== Variables
|
||||||
|
_Expr_ supports variables. The result of an expression can be stored in a variable and reused in other espressions simply specifying the name of the variable as an operand.
|
||||||
|
|
||||||
|
==== Multi-expression
|
||||||
|
An input text valid for _Expr_ can contain more than an expression. Expressions are separated by [blue]`;` (semicolon). When an input contains two or more expressions it is called _multi-expression_.
|
||||||
|
|
||||||
|
_Expr_ parses and computes each expression of a multi-espression, from the left to the right. If all expressions are computed without errors, it only returns the value of the last, the right most.
|
||||||
|
|
||||||
|
The result of each expression of a multi-expression is stored in an automatic variable named _last_. In this way, each expression can refer to the result of the previous one without the need to assign that value to a new dedicated variable.
|
||||||
|
|
||||||
|
==== Calculation context
|
||||||
|
All objects, such as variables and functions, created during the calculation of an expression are stored in a memory called _context_.
|
||||||
|
|
||||||
|
The expression context is analogous to the stack-frame of other programming languages. When a function is called, a new context is allocated to store local definitions.
|
||||||
|
|
||||||
|
Function contexts are created by cloning the calling context. More details on this topic are given later in this document.
|
||||||
|
|
||||||
|
_Expr_ creates and keeps a inner _global context_ where it stores imported functions, either from builtin or plugin modules. To perform calculations, the calling program must provide its own context; this is the _main context_. All calculations take place in this context. As mentioned eralier, when a function is called, a new context is created by cloning the calling context. The createt context can be called _function context_.
|
||||||
|
|
||||||
|
Imported functions are registerd in the _global context_. When an expression first calls an imported function, that function is linked to the current context; this can be the _main context_ or a _function context_.
|
||||||
|
|
||||||
|
=== `dev-expr` test tool
|
||||||
|
Before we begin to describe the syntax of _Expr_, it is worth introducing _dev-expr_ because it will be used to show many examples of expressions.
|
||||||
|
|
||||||
|
`dev-expr` is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, in additon to the automatic verification test suite based on the Go test framework, `dev-expr` provided an important aid for quickly testing of new features during their development.
|
||||||
|
|
||||||
|
`dev-expr` can work as a _REPL_, _**R**ead-**E**xecute-**P**rint-**L**oop_, or it can process expression acquired from files or standard input.
|
||||||
|
|
||||||
|
The program can be downloaded from https://git.portale-stac.it/go-pkg/-/packages/generic/dev-expr/[dev-expr].
|
||||||
|
|
||||||
|
Here are some examples of execution.
|
||||||
|
|
||||||
|
.Run `dev-expr` in REPL mode and ask for help
|
||||||
|
[source,shell]
|
||||||
|
----
|
||||||
|
# Type 'exit' or Ctrl+D to quit the program.
|
||||||
|
|
||||||
|
[user]$ ./dev-expr
|
||||||
|
dev-expr -- Expressions calculator v1.10.0(build 14),2024/06/17 (celestino.amoroso@portale-stac.it)
|
||||||
|
Based on the Expr package v0.19.0
|
||||||
|
Type help to get the list of available commands
|
||||||
|
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
|
||||||
|
>>> help
|
||||||
|
--- REPL commands:
|
||||||
|
source -- Load a file as input
|
||||||
|
tty -- Enable/Disable ansi output <1>
|
||||||
|
base -- Set the integer output base: 2, 8, 10, or 16
|
||||||
|
exit -- Exit the program
|
||||||
|
help -- Show command list
|
||||||
|
ml -- Enable/Disable multi-line output
|
||||||
|
mods -- List builtin modules
|
||||||
|
output -- Enable/Disable printing expression results. Options 'on', 'off', 'status'
|
||||||
|
|
||||||
|
--- Command line options:
|
||||||
|
-b <builtin> Import builtin modules.
|
||||||
|
<builtin> can be a list of module names or a glob-pattern.
|
||||||
|
Use the special value 'all' or the pattern '*' to import all modules.
|
||||||
|
-e <expression> Evaluate <expression> instead of standard-input
|
||||||
|
-i Force REPL operation when all -e occurences have been processed
|
||||||
|
-h, --help, help Show this help menu
|
||||||
|
-m, --modules List all builtin modules
|
||||||
|
--noout Disable printing of expression results
|
||||||
|
-p Print prefix form
|
||||||
|
-t Print tree form <2>
|
||||||
|
-v, --version Show program version
|
||||||
|
>>>
|
||||||
|
----
|
||||||
|
|
||||||
|
<1> Only available for single fraction values
|
||||||
|
<2> Work in progress
|
||||||
|
|
||||||
|
.REPL examples
|
||||||
|
[source,shell]
|
||||||
|
----
|
||||||
|
[user]$ ./dev-expr
|
||||||
|
dev-expr -- Expressions calculator v1.10.0(build 14),2024/06/17 (celestino.amoroso@portale-stac.it)
|
||||||
|
Based on the Expr package v0.19.0
|
||||||
|
Type help to get the list of available commands
|
||||||
|
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
|
||||||
|
|
||||||
|
>>> 2+3
|
||||||
|
5
|
||||||
|
>>> 2+3*(4-1.5)
|
||||||
|
9.5
|
||||||
|
>>> 0xFD + 0b1 + 0o1 <1>
|
||||||
|
255
|
||||||
|
>>> 1|2 + 2|3 <2>
|
||||||
|
7|6
|
||||||
|
>>> ml <3>
|
||||||
|
>>> 1|2 + 2|3
|
||||||
|
7
|
||||||
|
-
|
||||||
|
6
|
||||||
|
>>> 4+2 but 5|2+0.5 <4>
|
||||||
|
3
|
||||||
|
>>> 4+2; 5|2+0.5 <5>
|
||||||
|
3
|
||||||
|
>>>
|
||||||
|
----
|
||||||
|
|
||||||
|
<1> Number bases: 0x = _hexadecimal_, 0o = _octal_, 0b = _binary_.
|
||||||
|
<2> Fractions: _numerator_ | _denominator_.
|
||||||
|
<3> Activate multi-line output of fractions.
|
||||||
|
<4> But operator, see <<_but_operator>>.
|
||||||
|
<5> Multi-expression: the same result of the previous single expression but this it is obtained with two separated calculations.
|
||||||
|
|
||||||
== Data types
|
== Data types
|
||||||
_Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists.
|
_Expr_ has its type system which is a subset of Golang's type system. It supports numerical, string, relational, boolean expressions, and mixed-type lists and maps.
|
||||||
|
|
||||||
=== Numbers
|
=== Numbers
|
||||||
Numbers can be integers (GO int64) or float (GO float64). In mixed operations involving integers and floats, integers are automatically promoted to floats.
|
_Expr_ supports three type of numbers:
|
||||||
|
|
||||||
|
. [blue]#Integers#
|
||||||
|
. [blue]#Floats#
|
||||||
|
. [blue]#Factions#
|
||||||
|
|
||||||
|
In mixed operations involving integers, fractions and floats, automatic type promotion to the largest type take place.
|
||||||
|
|
||||||
|
==== Integers
|
||||||
|
__Expr__'s integers are a subset of the integer set. Internally they are stored as Golang _int64_ values.
|
||||||
|
|
||||||
|
.Integer literal syntax
|
||||||
|
====
|
||||||
|
*_integer_* = [_sign_] _digit-seq_ +
|
||||||
|
_sign_ = "**+**" | "**-**" +
|
||||||
|
_digit-seq_ = _dec-seq_ | _bin-seq_ | _oct-seq_ | _hex-seq_ +
|
||||||
|
_dec-seq_ = {__dec-digit__} +
|
||||||
|
_dec-digit_ = "**0**"|"**1**"|...|"**9**" +
|
||||||
|
_bin-seq_ = "**0b**"{__bin-digit__} +
|
||||||
|
_bin-digit_ = "**0**"|"**1**" +
|
||||||
|
_oct-seq_ = "**0o**"{__oct-digit__} +
|
||||||
|
_oct-digit_ = "**0**"|"**1**"|...|"**7**" +
|
||||||
|
_hex-seq_ = "**0x**"{__hex-digit__} +
|
||||||
|
_hex-digit_ = "**0**"|"**1**"|...|"**9**"|"**a**"|...|"**z**"|"**A**"|...|"**Z**"
|
||||||
|
====
|
||||||
|
|
||||||
|
Value range: *-9223372036854775808* to *9223372036854775807*
|
||||||
|
|
||||||
.Arithmetic operators
|
.Arithmetic operators
|
||||||
[cols="^1,^2,6,4"]
|
[cols="^1,^2,6,4"]
|
||||||
|===
|
|===
|
||||||
| Symbol | Operation | Description | Examples
|
| Symbol | Operation | Description | Examples
|
||||||
|
| [blue]`+` | _sum_ | Add two values | [blue]`-1 + 2` -> 1
|
||||||
| [blue]`+` / [blue]`-` | _change sign_ | Change the sign of values | [blue]`-1` _[-1]_ +
|
| [blue]`-` | _subtraction_ | Subtract the right value from the left one | [blue]`3 - 1` -> 2
|
||||||
[blue]`-(+2)` _[-2]_
|
| [blue]`*` | _product_ | Multiply two values | [blue]`-1 * 2` -> -2
|
||||||
|
| [blue]`/` | _Division_ | Divide the left value by the right one^(*)^ | [blue]`-10 / 2` -> 5
|
||||||
| [blue]`+` | _sum_ | Add two values | [blue]`-1 + 2` _[1]_ +
|
| [blue]`%` | _Modulo_ | Remainder of the integer division | [blue]`5 % 2` -> 1
|
||||||
[blue]`4 + 0.5` _[4.5]_
|
|
||||||
|
|
||||||
| [blue]`-` | _subtraction_ | Subtract the right value from the left one | [blue]`3 - 1` _[2]_ +
|
|
||||||
[blue]`4 - 0.5` _[3.5]_
|
|
||||||
|
|
||||||
| [blue]`*` | _product_ | Multiply two values | `-1 * 2` _[-2]_ +
|
|
||||||
[blue]`4 * 0.5` _[2.0]_
|
|
||||||
|
|
||||||
| [blue]`/` | _Division_ | Divide the left value by the right one | [blue]`-1 / 2` _[0]_ +
|
|
||||||
[blue]`1.0 / 2` _[0.5]_
|
|
||||||
|
|
||||||
| [blue]`./` | _Float division_ | Force float division | [blue]`-1 ./ 2` _[-0.5]_
|
|
||||||
|
|
||||||
| [blue]`%` | _Modulo_ | Remainder of the integer division | [blue]`5 % 2` _[1]_
|
|
||||||
|
|
||||||
|===
|
|===
|
||||||
|
|
||||||
=== String
|
^(*)^ See also the _float division_ [blue]`./` below.
|
||||||
Strings are character sequences enclosed between two double quote [blue]`"`. Example: [blue]`"I'm a string"`.
|
|
||||||
|
|
||||||
|
==== Floats
|
||||||
|
__Expr__'s floats are a subset of the rational number set. Note that they can't hold the exact value of an unlimited number; floats can only approximate them. Internally floats are stored as Golang's _float64_ values.
|
||||||
|
|
||||||
|
|
||||||
|
.Float literal syntax
|
||||||
|
====
|
||||||
|
*_float_* = [_sign_] _dec-seq_ "**.**" [_dec-seq_] [("**e**"|"**E**") [_sign_] _dec-seq_] +
|
||||||
|
_sign_ = "**+**" | "**-**" +
|
||||||
|
_dec-seq_ = _see-integer-literal-syntax_
|
||||||
|
====
|
||||||
|
|
||||||
|
.Examples
|
||||||
|
`>>>` [blue]`1.0` +
|
||||||
|
[green]`1`
|
||||||
|
|
||||||
|
`>>>` [blue]`0.123` +
|
||||||
|
[green]`0.123`
|
||||||
|
|
||||||
|
`>>>` [blue]`4.5e+3` +
|
||||||
|
[green]`4500`
|
||||||
|
|
||||||
|
`>>>` [blue]`4.5E-33` +
|
||||||
|
[green]`4.5e-33`
|
||||||
|
|
||||||
|
`>>>` [blue]`4.5E-3` +
|
||||||
|
[green]`0.0045`
|
||||||
|
|
||||||
|
`>>>` [blue]`4.5E10` +
|
||||||
|
[green]`4.5e+10`
|
||||||
|
|
||||||
|
|
||||||
|
.Arithmetic operators
|
||||||
|
[cols="^1,^2,6,4"]
|
||||||
|
|===
|
||||||
|
| Symbol | Operation | Description | Examples
|
||||||
|
| [blue]`+` | _sum_ | Add two values | [blue]`4 + 0.5` -> 4.5
|
||||||
|
| [blue]`-` | _subtraction_ | Subtract the right value from the left one | [blue]`4 - 0.5` -> 3.5
|
||||||
|
| [blue]`*` | _product_ | Multiply two values | [blue]`4 * 0.5` -> 2.0
|
||||||
|
| [blue]`/` | _Division_ | Divide the left value by the right one | [blue]`1.0 / 2` -> 0.5
|
||||||
|
| [blue]`./`| _Float division_ | Force float division | [blue]`-1 ./ 2` -> -0.5
|
||||||
|
|===
|
||||||
|
|
||||||
|
==== Fractions
|
||||||
|
_Expr_ also supports fractions. Fraction literals are made with two integers separated by a vertical bar `|`.
|
||||||
|
|
||||||
|
.Fraction literal syntax
|
||||||
|
====
|
||||||
|
*_fraction_* = [__sign__] (_num-den-spec_ | _float-spec_) +
|
||||||
|
_sign_ = "**+**" | "**-**" +
|
||||||
|
_num-den-spec_ = _digit-seq_ "**|**" _digit-seq_ +
|
||||||
|
_float-spec_ = _dec-seq_ "**.**" [_dec-seq_] "**(**" _dec-seq_ "**)**" +
|
||||||
|
_dec-seq_ = _see-integer-literal-syntax_ +
|
||||||
|
_digit-seq_ = _see-integer-literal-syntax_
|
||||||
|
====
|
||||||
|
|
||||||
|
.Examples
|
||||||
|
`>>>` [blue]`1 | 2` +
|
||||||
|
[green]`1|2`
|
||||||
|
|
||||||
|
`>>>` [blue]`4|6` [gray]_// Fractions are always reduced to their lowest terms_ +
|
||||||
|
[green]`2|3`
|
||||||
|
|
||||||
|
`>>>` [blue]`1|2 + 2|3` +
|
||||||
|
[green]`7|6`
|
||||||
|
|
||||||
|
`>>>` [blue]`1|2 * 2|3` +
|
||||||
|
[green]`1|3`
|
||||||
|
|
||||||
|
`>>>` [blue]`1|2 / 1|3` +
|
||||||
|
[green]`3|2`
|
||||||
|
|
||||||
|
`>>>` [blue]`1|2 ./ 1|3` [gray]_// Force decimal division_ +
|
||||||
|
[green]`1.5`
|
||||||
|
|
||||||
|
`>>>` [blue]`-1|2` +
|
||||||
|
[green]`-1|2`
|
||||||
|
|
||||||
|
`>>>` [blue]`1|-2` [gray]_// Invalid sign specification_ +
|
||||||
|
[red]_Eval Error: [1:3] infix operator "|" requires two non-nil operands, got 1_
|
||||||
|
|
||||||
|
`>>>` [blue]`1|(-2)` +
|
||||||
|
[green]`-1|2`
|
||||||
|
|
||||||
|
|
||||||
|
Fractions can be used together with integers and floats in expressions.
|
||||||
|
|
||||||
|
.Examples
|
||||||
|
`>>>` [blue]`1|2 + 5` +
|
||||||
|
[green]`11|2`
|
||||||
|
|
||||||
|
`>>>` [blue]`4 - 1|2` +
|
||||||
|
[green]`7|2`
|
||||||
|
|
||||||
|
`>>>` [blue]`1.0 + 1|2` +
|
||||||
|
[green]`1.5`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
=== Strings
|
||||||
|
Strings are character sequences enclosed between two double quote [blue]`"`.
|
||||||
|
|
||||||
|
.Examples
|
||||||
|
`>>>` [blue]`"I'm a string"` +
|
||||||
|
[green]`I'm a string`
|
||||||
|
|
||||||
|
`>>>` [blue]`"123abc?!"` +
|
||||||
|
[green]`123abc?!`
|
||||||
|
|
||||||
|
`>>>` [blue]`"123\nabc"` +
|
||||||
|
[green]`123` +
|
||||||
|
[green]`abc`
|
||||||
|
|
||||||
|
`>>>` [blue]`"123\tabc"` +
|
||||||
|
[green]`123{nbsp}{nbsp}{nbsp}{nbsp}abc`
|
||||||
|
|
||||||
Some arithmetic operators can also be used with strings.
|
Some arithmetic operators can also be used with strings.
|
||||||
|
|
||||||
@@ -73,212 +315,462 @@ Some arithmetic operators can also be used with strings.
|
|||||||
|===
|
|===
|
||||||
| Symbol | Operation | Description | Examples
|
| Symbol | Operation | Description | Examples
|
||||||
|
|
||||||
| [blue]`+` | _concatenation_ | Join two strings or two _stringable_ values | [blue]`"one" + "two"` _["onetwo"]_ +
|
| [blue]`+` | _concatenation_ | Join two strings or two _stringable_ values | [blue]`"one" + "two"` -> _"onetwo"_ +
|
||||||
[blue]`"one" + 2` _["one2"]_
|
[blue]`"one" + 2` -> _"one2"_
|
||||||
|
|
||||||
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` _["oneone"]_
|
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` -> _"oneone"_
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
The items of strings can be accessed using the square `[]` operator.
|
||||||
|
|
||||||
=== Boolean
|
.Item access syntax
|
||||||
Boolean data type has two values only: _true_ and _false_. Relational and Boolean expressions produce Boolean values.
|
====
|
||||||
|
*_item_* = _string-expr_ "**[**" _integer-expr_ "**]**"
|
||||||
|
====
|
||||||
|
|
||||||
|
.Sub-string syntax
|
||||||
|
====
|
||||||
|
*_sub-string_* = _string-expr_ "**[**" _integer-expr_ "**:**" _integer-expr_ "**]**"
|
||||||
|
====
|
||||||
|
|
||||||
.Relational operators
|
.String examples
|
||||||
|
`>>>` [blue]`s="abcd"` [gray]_// assign the string to variable s_ +
|
||||||
|
[green]`"abcd"`
|
||||||
|
|
||||||
|
`>>>` [blue]`s[1]` [gray]_// char at position 1 (starting from 0)_ +
|
||||||
|
[green]`"b"`
|
||||||
|
|
||||||
|
`>>>` [blue]`s.[-1]` [gray]_// char at position -1, the rightmost one_ +
|
||||||
|
[green]`"d"`
|
||||||
|
|
||||||
|
`>>>` [blue]`\#s` [gray]_// number of chars_ +
|
||||||
|
[gren]`4`
|
||||||
|
|
||||||
|
`>>>` [blue]`#"abc"` [gray]_// number of chars_ +
|
||||||
|
[green]`3`
|
||||||
|
|
||||||
|
`>>>` [blue]`s[1:3]` [gray]_// chars from position 1 to position 3 excluded_ +
|
||||||
|
[grean]`"bc"`
|
||||||
|
|
||||||
|
=== Booleans
|
||||||
|
Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relational and boolean expressions result in boolean values.
|
||||||
|
|
||||||
|
.Relational operators^(*)^
|
||||||
[cols="^1,^2,6,4"]
|
[cols="^1,^2,6,4"]
|
||||||
|===
|
|===
|
||||||
| Symbol | Operation | Description | Examples
|
| Symbol | Operation | Description | Examples
|
||||||
|
|
||||||
| [blue]`==` | _Equal_ | True if the left value is equal to the right one | [blue]`5 == 2` _[false]_ +
|
| [blue]`==` | _Equal_ | True if the left value is equal to the right one | [blue]`5 == 2` -> _false_ +
|
||||||
[blue]`"a" == "a"` _[true]_
|
[blue]`"a" == "a"` -> _true_
|
||||||
| [blue]`!=` | _Not Equal_ | True if the left value is NOT equal to the right one | [blue]`5 != 2` _[true]_ +
|
| [blue]`!=` | _Not Equal_ | True if the left value is NOT equal to the right one | [blue]`5 != 2` -> _true_ +
|
||||||
[blue]`"a" != "a"` _[false]_
|
[blue]`"a" != "a"` -> _false_
|
||||||
| [blue]`<` | _Less_ | True if the left value is less than the right one | [blue]`5 < 2` _[false]_ +
|
| [blue]`<` | _Less_ | True if the left value is less than the right one | [blue]`5 < 2` -> _false_ +
|
||||||
[blue]`"a" < "b"` _[true]_
|
[blue]`"a" < "b"` -> _true_
|
||||||
| [blue]`\<=` | _Less or Equal_ | True if the left value is less than or equal to the right one | [blue]`5 \<= 2` _[false]_ +
|
| [blue]`\<=` | _Less or Equal_ | True if the left value is less than or equal to the right one | [blue]`5 \<= 2` -> _false_ +
|
||||||
[blue]`"b" \<= "b"` _[true]_
|
[blue]`"b" \<= "b"` -> _true_
|
||||||
| [blue]`>` | _Greater_ | True if the left value is greater than the right one | [blue]`5 > 2` _[true]_ +
|
| [blue]`>` | _Greater_ | True if the left value is greater than the right one | [blue]`5 > 2` -> _true_ +
|
||||||
[blue]`"a" < "b"` _[false]_
|
[blue]`"a" < "b"` -> _false_
|
||||||
| [blue]`>=` | _Greater or Equal_ | True if the left value is greater than or equal to the right one | [blue]`5 >= 2` _[true]_ +
|
| [blue]`>=` | _Greater or Equal_ | True if the left value is greater than or equal to the right one | [blue]`5 >= 2` -> _true_ +
|
||||||
[blue]`"b" \<= "b"` _[true]_
|
[blue]`"b" \<= "b"` -> _true_
|
||||||
|===
|
|===
|
||||||
|
|
||||||
|
^(*)^ See also the [blue]`in` operator in the _list_ and _dictionary_ sections.
|
||||||
|
|
||||||
|
|
||||||
.Boolean operators
|
.Boolean operators
|
||||||
[cols="^2,^2,5,4"]
|
[cols="^2,^2,5,4"]
|
||||||
|===
|
|===
|
||||||
| Symbol | Operation | Description | Examples
|
| Symbol | Operation | Description | Examples
|
||||||
|
|
||||||
| [blue]`NOT` | _Not_ | True if the right value is false | [blue]`NOT true` _[false]_ +
|
| [blue]`NOT` | _Not_ | True if the right value is false | [blue]`NOT true` -> _false_ +
|
||||||
[blue]`NOT (2 < 1)` _[true]_
|
[blue]`NOT (2 < 1)` -> _true_
|
||||||
|
|
||||||
| [blue]`AND` / [blue]`&&` | _And_ | True if both left and right values are true | [blue]`false && true` _[false]_ +
|
| [blue]`AND` / [blue]`&&` | _And_ | True if both left and right values are true | [blue]`false && true` -> _false_ +
|
||||||
[blue]`"a" < "b" AND NOT (2 < 1)` _[true]_
|
[blue]`"a" < "b" AND NOT (2 < 1)` -> _true_
|
||||||
|
|
||||||
| [blue]`OR` / [blue]`\|\|` | _Or_ | True if at least one of the left and right values integers true| [blue]`false or true` _[true]_ +
|
| [blue]`OR` / [blue]`\|\|` | _Or_ | True if at least one of the left and right values integers true| [blue]`false or true` -> _true_ +
|
||||||
[blue]`"a" == "b" OR (2 == 1)` _[false]_
|
[blue]`"a" == "b" OR (2 == 1)` -> _false_
|
||||||
|===
|
|===
|
||||||
|
|
||||||
[CAUTION]
|
[CAUTION]
|
||||||
====
|
====
|
||||||
Currently, boolean operations are evaluated using _short cut evaluation_. This means that, if the left expression of operators [blue]`and` and [blue]`or` is sufficient to establish the result of the whole operation, the right expression would not evaluated at all.
|
Currently, boolean operations are evaluated using _short cut evaluation_. This means that, if the left expression of the [blue]`and` and [blue]`or` operators is sufficient to establish the result of the whole operation, the right expression would not be evaluated at all.
|
||||||
|
|
||||||
.Example
|
.Example
|
||||||
[source,go]
|
[source,go]
|
||||||
----
|
----
|
||||||
2 > (a=1) or (a=8) > 0; a // <1>
|
2 > (a=1) or (a=8) > 0; a // <1>
|
||||||
----
|
----
|
||||||
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and as a conseguence its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
|
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and as a conseguence its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
|
||||||
|
|
||||||
|
|
||||||
|
TIP: `dev-expr` provides the _ctrl()_ function that allows to change this behaviour.
|
||||||
====
|
====
|
||||||
|
|
||||||
=== List
|
=== Lists
|
||||||
_Expr_ supports list of mixed-type values, also specified by normal expressions.
|
_Expr_ supports list of mixed-type values, also specified by normal expressions. Internally, _Expr_'s lists are Go arrays.
|
||||||
|
|
||||||
.List examples
|
.List literal syntax
|
||||||
[source,go]
|
====
|
||||||
----
|
*_list_* = _empty-list_ | _non-empty-list_ +
|
||||||
[1, 2, 3] // List of integers
|
_empty-list_ = "**[]**" +
|
||||||
["one", "two", "three"] // List of strings
|
_non-empty-list_ = "**[**" _any-value_ {"**,**" _any-value} "**]**" +
|
||||||
["one", 2, false, 4.1] // List of mixed-types
|
====
|
||||||
["one"+1, 2.0*(9-2)] // List of expressions
|
|
||||||
[ [1,"one"], [2,"two"]] // List of lists
|
|
||||||
----
|
|
||||||
|
|
||||||
|
.Examples
|
||||||
|
`>>>` [blue]`[1,2,3]` [gray]_// List of integers_ +
|
||||||
|
[green]`[1, 2, 3]`
|
||||||
|
|
||||||
|
`>>>` [blue]`["one", "two", "three"]` [gray]_// List of strings_ +
|
||||||
|
[green]`["one", "two", "three"]`
|
||||||
|
|
||||||
|
`>>>` [blue]`["one", 2, false, 4.1]` [gray]_// List of mixed-types_ +
|
||||||
|
[green]`["one", 2, false, 4.1]`
|
||||||
|
|
||||||
|
`>>>` [blue]`["one"+1, 2.0*(9-2)]` [gray]_// List of expressions_ +
|
||||||
|
[green]`["one1", 14]`
|
||||||
|
|
||||||
|
`>>>` [blue]`[ [1,"one"], [2,"two"]]` [gray]_// List of lists_ +
|
||||||
|
[green]`[[1, "one"], [2, "two"]]`
|
||||||
|
|
||||||
.List operators
|
.List operators
|
||||||
[cols="^2,^2,5,4"]
|
[cols="^2,^2,5,4"]
|
||||||
|===
|
|===
|
||||||
| Symbol | Operation | Description | Examples
|
| Symbol | Operation | Description | Examples
|
||||||
|
|
||||||
| [blue]`+` | _Join_ | Joins two lists | [blue]`[1,2] + [3]` _[ [1,2,3] ]_
|
| [blue]`+` | _Join_ | Joins two lists | [blue]`[1,2] + [3]` -> _[1,2,3]_
|
||||||
|
| [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` -> _[1,3]_
|
||||||
| [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` _[ [1,3] ]_
|
| [blue]`>>` | _Front insertion_ | Insert an item in front | [blue]`0 >> [1,2]` -> _[0,1,2]_
|
||||||
|
| [blue]`<<` | _Back insertion_ | Insert an item at end | [blue]`[1,2] << 3` -> _[1,2,3]_
|
||||||
|
| [blue]`[]` | _Item at index_ | Item at given position | [blue]`[1,2,3][1]` -> _2_
|
||||||
|
| [blue]`in` | _Item in list_ | True if item is in list | [blue]`2 in [1,2,3]` -> _true_ +
|
||||||
|
[blue]`6 in [1,2,3]` -> _false_
|
||||||
|===
|
|===
|
||||||
|
|
||||||
== Variables
|
Array's items can be accessed using the index `[]` operator.
|
||||||
A variable is an identifier with an assigned value. Variables are stored in the object that implements the _ExprContext_ interface.
|
|
||||||
|
.Item access syntax
|
||||||
|
====
|
||||||
|
*_item_* = _list-expr_ "**[**" _integer-expr_ "**]**"
|
||||||
|
====
|
||||||
|
|
||||||
|
.Sub-array (or slice of array) syntax
|
||||||
|
====
|
||||||
|
*_slice_* = _string-expr_ "**[**" _integer-expr_ "**:**" _integer-expr_ "**]**"
|
||||||
|
====
|
||||||
|
|
||||||
|
.Items of list
|
||||||
|
`>>>` [blue]`[1,2,3].1` +
|
||||||
|
[green]`2`
|
||||||
|
|
||||||
|
`>>>` [blue]`list=[1,2,3]; list.1` +
|
||||||
|
[green]`2`
|
||||||
|
|
||||||
|
`>>>` [blue]`["one","two","three"].1` +
|
||||||
|
[green]`two`
|
||||||
|
|
||||||
|
`>>>` [blue]`list=["one","two","three"]; list.(2-1)` +
|
||||||
|
[green]`two`
|
||||||
|
|
||||||
|
`>>>` [blue]`list.(-1)` +
|
||||||
|
[green]`three`
|
||||||
|
|
||||||
|
`>>>` [blue]`list.(10)` +
|
||||||
|
[red]`Eval Error: [1:9] index 10 out of bounds`
|
||||||
|
|
||||||
|
`>>>` [blue]`#list` +
|
||||||
|
[green]`3`
|
||||||
|
|
||||||
|
`>>>` [blue]`index=2; ["a", "b", "c", "d"][index]` +
|
||||||
|
[green]`c`
|
||||||
|
|
||||||
|
|
||||||
|
`>>>` [blue]`["a", "b", "c", "d"][2:]` +
|
||||||
|
[green]`["c", "d"]`
|
||||||
|
|
||||||
|
|
||||||
|
=== Dictionaries
|
||||||
|
The _dictionary_, or _dict_, data-type is set of pairs _key/value_. It is also known as _map_ or _associative array_.
|
||||||
|
|
||||||
|
Dictionary literals are sequences of pairs separated by comma [blue]`,` enclosed between brace brackets.
|
||||||
|
|
||||||
|
.Dict literal syntax
|
||||||
|
====
|
||||||
|
*_dict_* = _empty-dict_ | _non-empty-dict_ +
|
||||||
|
_empty-dict_ = "**{}**" +
|
||||||
|
_non-empty-dict_ = "**{**" _key-scalar_ "**:**" _any-value_ {"**,**" _key-scalar_ "**:**" _any-value} "**}**" +
|
||||||
|
====
|
||||||
|
|
||||||
|
|
||||||
|
.Item access syntax
|
||||||
|
====
|
||||||
|
*_item_* = _dict-expr_ "**[**" _key-expr_ "**]**"
|
||||||
|
====
|
||||||
|
|
||||||
|
.Dict operators
|
||||||
|
[cols="^2,^2,4,5"]
|
||||||
|
|===
|
||||||
|
| Symbol | Operation | Description | Examples
|
||||||
|
|
||||||
|
| [blue]`+` | _Join_ | Joins two dicts | [blue]`{1:"one"}+{6:"six"}` -> _{1: "one", 6: "six"}_
|
||||||
|
| [blue]`[]` | _Dict item value_ | Item value of given key | [blue]`{"one":1, "two":2}["two"]` -> _2_
|
||||||
|
| [blue]`in` | _Key in dict_ | True if key is in dict | [blue]`"one" in {"one":1, "two":2}` -> _true_ +
|
||||||
|
[blue]`"six" in {"one":1, "two":2}` -> _false_
|
||||||
|
|===
|
||||||
|
|
||||||
.Examples
|
.Examples
|
||||||
[source,go]
|
`>>>` [blue]`{1:"one", 2:"two"}` +
|
||||||
----
|
[green]`{1: "one", 2: "two"}`
|
||||||
a=1
|
|
||||||
x = 5.2 * (9-3)
|
`>>>` [blue]`{"one":1, "two": 2}` +
|
||||||
x = 1; y = 2*x
|
[green]`{"one": 1, "two": 2}`
|
||||||
----
|
|
||||||
|
`>>>` [blue]`{"sum":1+2+3, "prod":1*2*3}` +
|
||||||
|
[green]`{"sum": 6, "prod": 6}`
|
||||||
|
|
||||||
|
`>>>` [blue]`{"one":1, "two":2}["two"]` +
|
||||||
|
[green]`2`
|
||||||
|
|
||||||
|
`>>>` [blue]`d={"one":1, "two":2}; d["six"]=6; d` +
|
||||||
|
[green]`{"two": 2, "one": 1, "six": 6}`
|
||||||
|
|
||||||
|
|
||||||
|
== Variables
|
||||||
|
_Expr_, like most programming languages, supports variables. A variable is an identifier with an assigned value. Variables are stored in _contexts_.
|
||||||
|
|
||||||
|
.Variable literal syntax
|
||||||
|
====
|
||||||
|
*_variable_* = _identifier_ "*=*" _any-value_ +
|
||||||
|
_identifier_ = _alpha_ {(_alpha_)|_dec-digit_|"*_*"} +
|
||||||
|
__alpha__ = "*a*"|"*b*"|..."*z*"|"*A*"|"*B*"|..."*Z*"
|
||||||
|
====
|
||||||
|
|
||||||
|
NOTE: The assign operator [blue]`=` returns the value assigned to the variable.
|
||||||
|
|
||||||
|
.Examples
|
||||||
|
`>>>` [blue]`a=1` +
|
||||||
|
[green]`1`
|
||||||
|
|
||||||
|
`>>>` [blue]`a_b=1+2` +
|
||||||
|
[green]`1+2`
|
||||||
|
|
||||||
|
`>>>` [blue]`a_b` +
|
||||||
|
[green]`3`
|
||||||
|
|
||||||
|
`>>>` [blue]`x = 5.2 * (9-3)` [gray]_// The assigned value has the typical approximation error of the float data-type_ +
|
||||||
|
[green]`31.200000000000003`
|
||||||
|
|
||||||
|
`>>>` [blue]`x = 1; y = 2*x` +
|
||||||
|
[green]`2`
|
||||||
|
|
||||||
|
`>>>` [blue]`_a=2` +
|
||||||
|
[red]`Parse Error: [1:2] unexpected token "_"`
|
||||||
|
|
||||||
|
`>>>` [blue]`1=2` +
|
||||||
|
[red]`Parse Error: assign operator ("=") must be preceded by a variable`
|
||||||
|
|
||||||
|
|
||||||
== Other operations
|
== Other operations
|
||||||
|
|
||||||
=== [blue]`;` operator
|
=== [blue]`;` operator
|
||||||
The semicolon operator [blue]`;` is an infixed pseudo-operator. It evaluates the left expression first and then the right expression. The latter is the final result.
|
The semicolon operator [blue]`;` is an infixed pseudo-operator. It evaluates the left expression first and then the right expression. The value of the latter is the final result.
|
||||||
|
|
||||||
|
.Mult-expression syntax
|
||||||
|
====
|
||||||
|
*_multi-expression_* = _expression_ {"**;**" _expression_ }
|
||||||
|
====
|
||||||
|
|
||||||
|
An expression that contains [blue]`;` is called a _multi-expression_ and each component expressione is called a _sub-expression_.
|
||||||
|
|
||||||
IMPORTANT: Technically [blue]`;` is not treated as a real operator. It acts as a separator in lists of expressions.
|
IMPORTANT: Technically [blue]`;` is not treated as a real operator. It acts as a separator in lists of expressions.
|
||||||
|
|
||||||
TIP: [blue]`;` can be used to set some variables before the final calculation.
|
TIP: [blue]`;` can be used to set some variables before the final calculation.
|
||||||
|
|
||||||
.Example
|
.Example
|
||||||
[source,go]
|
`>>>` [blue]`a=1; b=2; c=3; a+b+c` +
|
||||||
----
|
[green]`6`
|
||||||
a=1; b=2; c=3; a+b+c // returns 6
|
|
||||||
----
|
The value of each sub-expression is stored in the automatica variable _last_.
|
||||||
|
|
||||||
|
.Example
|
||||||
|
`>>>` [blue]`2+3; b=last+10; last` +
|
||||||
|
[green]`15`
|
||||||
|
|
||||||
|
|
||||||
=== [blue]`but` operator
|
=== [blue]`but` operator
|
||||||
[blue]`but` is an infixed operator. Its operands can be any type of expression. It evaluates the left expression first, then the right expression. The value of the right expression is the final result. Examples: [blue]`5 but 2` returns 2, [blue]`x=2*3 but x-1` returns 5.
|
[blue]`but` is an infixed operator. Its operands can be expressions of any type. It evaluates the left expression first, then the right expression. The value of the right expression is the final result.
|
||||||
|
|
||||||
[blue]`but` is very similar to [blue]`;`. The only difference is that [blue]`;` can't be used inside parenthesis [blue]`(` and [blue]`)`.
|
.Examples
|
||||||
|
[blue]`5 but 2` +
|
||||||
|
[green]`2` +
|
||||||
|
[blue]`x=2*3 but x-1` +
|
||||||
|
[green]`5`.
|
||||||
|
|
||||||
|
[blue]`but` behavior is very similar to [blue]`;`. The only difference is that [blue]`;` is not a true operator and can't be used inside parenthesis [blue]`(` and [blue]`)`.
|
||||||
|
|
||||||
=== Assignment operator [blue]`=`
|
=== Assignment operator [blue]`=`
|
||||||
The assignment operator [blue]`=` is used to define variables in the evaluation context or to change their value (see _ExprContext_).
|
The assignment operator [blue]`=` is used to define variables or to change their value in the evaluation context (see _ExprContext_).
|
||||||
|
|
||||||
The value on the left side of [blue]`=` must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.
|
The value on the left side of [blue]`=` must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.
|
||||||
|
|
||||||
.Example
|
.Example
|
||||||
[source,go]
|
`>>>` [blue]`a=15+1`
|
||||||
----
|
[green]`16`
|
||||||
a=15+1 // returns 16
|
|
||||||
----
|
|
||||||
|
|
||||||
=== Selector operator [blue]`? : ::`
|
=== Selector operator [blue]`? : ::`
|
||||||
The _selector operator_ is very similar to the _switch/case/default_ statement available in many programming languages.
|
The _selector operator_ is very similar to the _switch/case/default_ statement available in many programming languages.
|
||||||
|
|
||||||
.Syntax
|
.Selector literal Syntax
|
||||||
[source,bnf]
|
_selector-operator_ = _select-expression_ "*?*" _selector-case_ { "*:*" _selector-case_ } ["*::*" _default-multi-expression_] +
|
||||||
----
|
_selector-case_ = [_match-list_] _case-value_ +
|
||||||
<selector-operator> ::= <select-expression> "?" <selector-case> { ":" <selector-case> } ["::" <default-multi-expression>]
|
_match-list_ = "*[*" _item_ {"*,*" _items_} "*]*" +
|
||||||
<selector-case> ::= [<match-list>] <case-value>
|
_item_ = _expression_ +
|
||||||
<match-list> ::= "["<item>{","<items>}"]"
|
_case-multi-expression_ = "*{*" _multi-expression_ "*}*" +
|
||||||
<item> ::= <expression
|
_multi-expression_ = _expression_ { "*;*" _expression_ } +
|
||||||
<case-multi-expression> ::= "{" <multi-expression> "}"
|
_default-multi-expression_ = _multi-expression_
|
||||||
<multi-expression> ::= <expression> {";" <expression>}
|
|
||||||
----
|
|
||||||
|
|
||||||
In other words, the selector operator evaluates the expression (`<select-expression>`) on the left-hand side of the `?` symbol; it then compares the result obtained with the values listed in the `<match-list>`'s. If the comparision find a match with a value in a match-list, the associated `<case-multi-expression>` is evaluted, and its result will be the final result of the selection operation.
|
In other words, the selector operator evaluates the _select-expression_ on the left-hand side of the [blue]`?` symbol; it then compares the result obtained with the values listed in the __match-list__'s, from left to right. If the comparision finds a match with a value in a _match-list_, the associated _case-multi-expression_ is evaluted, and its result will be the final result of the selection operation.
|
||||||
|
|
||||||
The match lists are optional. In that case, the position, from left to right, of the `<selector-case>` is used as match-list. Of course, that only works if the select-expression results in an integer.
|
The match lists are optional. In that case, the position, from left to right, of the _selector-case_ is used as _match-list_. Of course, that only works if the _select-expression_ results in an integer.
|
||||||
|
|
||||||
The `:` symbol (colon) is the separator of the selector-cases. Note that if the value of the select-expression does not match any match-list, an error will be issued. Therefore, it is strongly recommended to provide a default (multi-)expression introduced by the `::` symbol (double-colon). Also note that the default expression has no match-list.
|
|
||||||
|
|
||||||
|
The [blue]`:` symbol (colon) is the separator of the selector-cases. Note that if the value of the _select-expression_ does not match any _match-list_, an error will be issued. Therefore, it is strongly recommended to provide a default (multi-)expression introduced by the [blue]`::` symbol (double-colon). Also note that the default expression has no _match-list_.
|
||||||
|
|
||||||
.Examples
|
.Examples
|
||||||
[source,go]
|
`>>>` [blue]`1 ? {"a"} : {"b"}` +
|
||||||
----
|
[green]`b`
|
||||||
1 ? {"a"} : {"b"} // returns "b"
|
|
||||||
10 ? {"a"} : {"b"} :: {"c"} // returns "c"
|
`>>>` [blue]`10 ? {"a"} : {"b"} {2c} {"c"}` +
|
||||||
10 ? {"a"} :[true, 2+8] {"b"} :: {"c"} // returns "b"
|
[green]`c`
|
||||||
10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"} // error: "... case list in default clause"
|
|
||||||
10 ? {"a"} :[10] {x="b" but x} :: {"c"} // returns "b"
|
`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} {2c} {"c"}` +
|
||||||
10 ? {"a"} :[10] {x="b"; x} :: {"c"} // returns "b"
|
[green]`b`
|
||||||
10 ? {"a"} : {"b"} // error: "... no case catches the value (10) of the selection expression
|
|
||||||
----
|
`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} {2c} [10] {"c"}` +
|
||||||
|
[red]`Parse Error: [1:34] case list in default clause`
|
||||||
|
|
||||||
|
`>>>` [blue]`10 ? {"a"} :[10] {x="b" but x} {2c} {"c"}` +
|
||||||
|
[green]`b`
|
||||||
|
|
||||||
|
`>>>` [blue]`10 ? {"a"} :[10] {x="b"; x} {2c} {"c"}` +
|
||||||
|
[green]`b`
|
||||||
|
|
||||||
|
`>>>` [blue]`10 ? {"a"} : {"b"}` +
|
||||||
|
[red]`Eval Error: [1:3] no case catches the value (10) of the selection expression`
|
||||||
|
|
||||||
|
|
||||||
|
=== Variable default value [blue]`??` and [blue]`?=`
|
||||||
|
The left operand of these two operators must be a variable. The right operator can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression.
|
||||||
|
|
||||||
|
IMPORTANT: If the left variable is defined, the right expression is not evaluated at all.
|
||||||
|
|
||||||
|
The [blue]`??` operator do not change the status of the left variable.
|
||||||
|
|
||||||
|
The [blue]`?=` assigns the calculated value of the right expression to the left variable.
|
||||||
|
|
||||||
|
.Examples
|
||||||
|
`>>>` [blue]`var ?? (1+2)`' +
|
||||||
|
[green]`3`
|
||||||
|
|
||||||
|
`>>>` [blue]`var` +
|
||||||
|
[red]`Eval Error: undefined variable or function "var"`
|
||||||
|
|
||||||
|
`>>>` [blue]`var ?= (1+2)` +
|
||||||
|
[green]`3`
|
||||||
|
|
||||||
|
`>>>` [blue]`var`
|
||||||
|
[green]`3`
|
||||||
|
|
||||||
|
NOTE: These operators have a high priority, in particular higher than the operator [blue]`=`.
|
||||||
|
|
||||||
== Priorities of operators
|
== Priorities of operators
|
||||||
The table below shows all supported operators by decreasing priorities.
|
The table below shows all supported operators by decreasing priorities.
|
||||||
|
|
||||||
.Operators priorities
|
.Operators priorities
|
||||||
[cols="^2,^2,^2,^5,^5"]
|
[cols="^2,^2,^2,^5,^6"]
|
||||||
|===
|
|===
|
||||||
| Priority | Operators | Position | Operation | Operands and results
|
| Priority | Operators | Position | Operation | Operands and results
|
||||||
|
|
||||||
.1+|*ITEM*| [blue]`.` | _Infix_ | _Item_| _collection_ `"."` _key_ -> _any_
|
.2+|*ITEM*| [blue]`[`...`]` | _Postfix_ | _List item_| _list_ `[` _integer_ `]` -> _any_
|
||||||
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `"++"` -> _integer_
|
| [blue]`[`...`]` | _Postfix_ | _Dict item_ | _dict_ `[` _any_ `]` -> _any_
|
||||||
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `"++"` -> _any_
|
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `++` -> _integer_
|
||||||
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `"!"` -> _integer_
|
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `++` -> _any_
|
||||||
.3+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| (`"+"`\|`"-"`) _number_ -> _number_
|
.2+|*DEFAULT*| [blue]`??` | _Infix_ | _Default value_| _variable_ `??` _any-expr_ -> _any_
|
||||||
| [blue]`#` | _Prefix_ | _Lenght-of_ | `"#"` _collection_ -> _integer_
|
| [blue]`?=` | _Infix_ | _Default/assign value_| _variable_ `?=` _any-expr_ -> _any_
|
||||||
| [blue]`#` | _Prefix_ | _Size-of_ | `"#"` _iterator_ -> _integer_
|
.1+| *ITER*^1^| [blue]`()` | _Prefix_ | _Iterator value_ | `()` _iterator_ -> _any_
|
||||||
.5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ `"*"` _number_ -> _number_
|
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `!` -> _integer_
|
||||||
| [blue]`*` | _Infix_ | _String-repeat_ | _string_ `"*"` _integer_ -> _string_
|
.3+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| (`+`\|`-`) _number_ -> _number_
|
||||||
| [blue]`/` | _Infix_ | _Division_ | _number_ `"/"` _number_ -> _number_
|
| [blue]`#` | _Prefix_ | _Lenght-of_ | `#` _collection_ -> _integer_
|
||||||
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `"./"` _number_ -> _float_
|
| [blue]`#` | _Prefix_ | _Size-of_ | `#` _iterator_ -> _integer_
|
||||||
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `"%"` _integer_ -> _integer_
|
.2+|*SELECT*| [blue]`? : ::` | _Multi-Infix_ | _Case-Selector_ | _any-expr_ `?` _case-list_ _case-expr_ `:` _case-list_ _case-expr_ ... `::` _default-expr_ -> _any_
|
||||||
.5+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `"+"` _number_ -> _number_
|
| [blue]`? : ::` | _Multi-Infix_ | _Index-Selector_ | _int-expr_ `?` _case-expr_ `:` _case-expr_ ... `::` _default-expr_ -> _any_
|
||||||
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `"+"` (_string_\|_number_) -> _string_
|
.1+|*FRACT*| [blue]`\|` | _Infix_ | _Fraction_ | _integer_ `\|` _integer_ -> _fraction_
|
||||||
| [blue]`+` | _Infix_ | _List-join_ | _list_ `"+"` _list_ -> _list_
|
.5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ `*` _number_ -> _number_
|
||||||
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `"-"` _number_ -> _number_
|
| [blue]`*` | _Infix_ | _String-repeat_ | _string_ `*` _integer_ -> _string_
|
||||||
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `"-"` _list_ -> _list_
|
| [blue]`/` | _Infix_ | _Division_ | _number_ `/` _number_ -> _number_
|
||||||
.6+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ `"<"` _comparable_ -> _boolean_
|
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `./` _number_ -> _float_
|
||||||
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `"\<="` _comparable_ -> _boolean_
|
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `%` _integer_ -> _integer_
|
||||||
| [blue]`>` | _Infix_ | _greater_ | _comparable_ `">"` _comparable_ -> _boolean_
|
.6+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `+` _number_ -> _number_
|
||||||
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ `">="` _comparable_ -> _boolean_
|
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `+` (_string_\|_number_) -> _string_
|
||||||
| [blue]`==` | _Infix_ | _equal_ | _comparable_ `"=="` _comparable_ -> _boolean_
|
| [blue]`+` | _Infix_ | _List-join_ | _list_ `+` _list_ -> _list_
|
||||||
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ `"!="` _comparable_ -> _boolean_
|
| [blue]`+` | _Infix_ | _Dict-join_ | _dict_ `+` _dict_ -> _dict_
|
||||||
.1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | `"not"` _boolean_ -> _boolean_
|
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `-` _number_ -> _number_
|
||||||
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ `"and"` _boolean_ -> _boolean_
|
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `-` _list_ -> _list_
|
||||||
| [blue]`&&` | _Infix_ | _and_ | _boolean_ `"&&"` _boolean_ -> _boolean_
|
.8+|*RELATION*| [blue]`<` | _Infix_ | _Less_ | _comparable_ `<` _comparable_ -> _boolean_
|
||||||
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ `"or"` _boolean_ -> _boolean_
|
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `\<=` _comparable_ -> _boolean_
|
||||||
| [blue]`\|\|` | _Infix_ | _or_ | _boolean_ `"\|\|"` _boolean_ -> _boolean_
|
| [blue]`>` | _Infix_ | _Greater_ | _comparable_ `>` _comparable_ -> _boolean_
|
||||||
.1+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
|
| [blue]`>=` | _Infix_ | _Greater-equal_ | _comparable_ `>=` _comparable_ -> _boolean_
|
||||||
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
|
| [blue]`==` | _Infix_ | _Equal_ | _comparable_ `==` _comparable_ -> _boolean_
|
||||||
|
| [blue]`!=` | _Infix_ | _Not-equal_ | _comparable_ `!=` _comparable_ -> _boolean_
|
||||||
|
| [blue]`in` | _Infix_ | _Member-of-list_ | _any_ `in` _list_ -> _boolean_
|
||||||
|
| [blue]`in` | _Infix_ | _Key-of-dict_ | _any_ `in` _dict_ -> _boolean_
|
||||||
|
.1+|*NOT*| [blue]`not` | _Prefix_ | _Not_ | `not` _boolean_ -> _boolean_
|
||||||
|
.2+|*AND*| [blue]`and` | _Infix_ | _And_ | _boolean_ `and` _boolean_ -> _boolean_
|
||||||
|
| [blue]`&&` | _Infix_ | _And_ | _boolean_ `&&` _boolean_ -> _boolean_
|
||||||
|
.2+|*OR*| [blue]`or` | _Infix_ | _Or_ | _boolean_ `or` _boolean_ -> _boolean_
|
||||||
|
| [blue]`\|\|` | _Infix_ | _Or_ | _boolean_ `\|\|` _boolean_ -> _boolean_
|
||||||
|
.3+|*ASSIGN*| [blue]`=` | _Infix_ | _Assignment_ | _identifier_ `=` _any_ -> _any_
|
||||||
|
| [blue]`>>` | _Infix_ | _Front-insert_ | _any_ `>>` _list_ -> _list_
|
||||||
|
| [blue]`<<` | _Infix_ | _Back-insert_ | _list_ `<<` _any_ -> _list_
|
||||||
|
.1+|*BUT*| [blue]`but` | _Infix_ | _But_ | _any_ `but` _any_ -> _any_
|
||||||
|
.1+|*RANGE*| [blue]`:` | _Infix_ | _Index-range_ | _integer_ `:` _integer_ -> _integer-pair_
|
||||||
|===
|
|===
|
||||||
|
|
||||||
== Functions
|
^1^ Experimental
|
||||||
Functions in _Expr_ are very similar to functions in many programming languages.
|
|
||||||
|
|
||||||
In _Expr_ functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator [blue]`@` it is possibile to export local definition to the calling context.
|
|
||||||
|
== Functions
|
||||||
|
Functions in _Expr_ are very similar to functions available in many programming languages. Actually, _Expr_ supports two types of function, _expr-functions_ and _go-functions_.
|
||||||
|
|
||||||
|
* _expr-functions_ are defined using _Expr_'s syntax. They can be passed as arguments to other functions and can be returned from functions. Moreover, they bind themselves to the defining context, thus becoming closures.
|
||||||
|
* _go-functions_ are regular Golang functions callable from _Expr_ expressions. They are defined in Golang source files called _modules_ and compiled within the _Expr_ package. To make Golang functions available in _Expr_ contextes, it is required to _import_ the module in which they are defined.
|
||||||
|
|
||||||
|
|
||||||
|
=== _Expr_ function definition
|
||||||
|
A function is identified and referenced by its name. It can have zero or more parameter. _Expr_ functions also support optional parameters.
|
||||||
|
|
||||||
|
. Expr's function definition syntax
|
||||||
|
====
|
||||||
|
*_function-definition_* = _identifier_ "**=**" "**func(**" [_param-list_] "**)**" "**{**" _multi-expression_ "**}**"
|
||||||
|
_param_list_ = _required-param-list_ [ "**,**" _optional-param-list_ ]
|
||||||
|
_required-param-list_ = _identifier_ { "**,**" _identifier_ }
|
||||||
|
_optional-param-list_ = _optional-parm_ { "**,**" _optional-param_ }
|
||||||
|
_optional-param_ = _identifier_ "**=**" _any-expr_
|
||||||
|
====
|
||||||
|
|
||||||
|
.Examples
|
||||||
|
#TODO#
|
||||||
|
|
||||||
|
=== _Golang_ function definition
|
||||||
|
Description of how to define Golan functions and how to bind them to _Expr_ are topics treated in another document that I'll write, one day, maybe.
|
||||||
|
|
||||||
=== Function calls
|
=== Function calls
|
||||||
#TODO: function calls operations#
|
#TODO: function calls operations#
|
||||||
|
|
||||||
=== Function definitions
|
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.
|
||||||
#TODO: function definitions operations#
|
|
||||||
|
|
||||||
|
== Iterators
|
||||||
|
#TODO: function calls operations#
|
||||||
|
|
||||||
== Builtins
|
== Builtins
|
||||||
#TODO: builtins#
|
#TODO: builtins#
|
||||||
@@ -289,4 +781,7 @@ In _Expr_ functions compute values in a local context (scope) that do not make e
|
|||||||
[blue]_import([grey]#<source-file>#)_ loads the multi-expression contained in the specified source and returns its value.
|
[blue]_import([grey]#<source-file>#)_ loads the multi-expression contained in the specified source and returns its value.
|
||||||
|
|
||||||
|
|
||||||
|
== Plugins
|
||||||
|
#TODO: plugins#
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+2207
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// expr-context.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
// ----Expression Context
|
||||||
|
type ExprContext interface {
|
||||||
|
Clone() ExprContext
|
||||||
|
// Merge(ctx ExprContext)
|
||||||
|
SetParent(ctx ExprContext)
|
||||||
|
GetParent() (ctx ExprContext)
|
||||||
|
GetVar(varName string) (value any, exists bool)
|
||||||
|
GetLast() any
|
||||||
|
SetVar(varName string, value any)
|
||||||
|
UnsafeSetVar(varName string, value any)
|
||||||
|
|
||||||
|
EnumVars(func(name string) (accept bool)) (varNames []string)
|
||||||
|
VarCount() int
|
||||||
|
DeleteVar(varName string)
|
||||||
|
|
||||||
|
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
|
||||||
|
FuncCount() int
|
||||||
|
DeleteFunc(funcName string)
|
||||||
|
|
||||||
|
GetFuncInfo(name string) (item ExprFunc, exists bool)
|
||||||
|
Call(name string, args []any) (result any, err error)
|
||||||
|
RegisterFuncInfo(info ExprFunc)
|
||||||
|
RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) error
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// expr-function.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
// ---- Functor interface
|
||||||
|
type Functor interface {
|
||||||
|
Typer
|
||||||
|
Invoke(ctx ExprContext, name string, args []any) (result any, err error)
|
||||||
|
SetFunc(info ExprFunc)
|
||||||
|
GetFunc() ExprFunc
|
||||||
|
GetParams() []ExprFuncParam
|
||||||
|
GetDefinitionContext() ExprContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Function Param Info
|
||||||
|
type ExprFuncParam interface {
|
||||||
|
Name() string
|
||||||
|
Type() string
|
||||||
|
IsDefault() bool
|
||||||
|
IsOptional() bool
|
||||||
|
IsRepeat() bool
|
||||||
|
DefaultValue() any
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Function Info
|
||||||
|
type ExprFunc interface {
|
||||||
|
Formatter
|
||||||
|
Name() string
|
||||||
|
MinArgs() int
|
||||||
|
MaxArgs() int
|
||||||
|
Functor() Functor
|
||||||
|
Params() []ExprFuncParam
|
||||||
|
ReturnType() string
|
||||||
|
PrepareCall(parentCtx ExprContext, name string, varParams *[]any) (ctx ExprContext, err error)
|
||||||
|
AllocContext(parentCtx ExprContext) (ctx ExprContext)
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// expr.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
// ----Expression interface
|
||||||
|
type Expr interface {
|
||||||
|
Typer
|
||||||
|
Eval(ctx ExprContext) (result any, err error)
|
||||||
|
String() string
|
||||||
|
}
|
||||||
@@ -1,84 +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},
|
|
||||||
}
|
|
||||||
|
|
||||||
succeeded := 0
|
|
||||||
failed := 0
|
|
||||||
|
|
||||||
inputs1 := []inputType{
|
|
||||||
/* 1 */ {`
|
|
||||||
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},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, input := range inputs1 {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
builtin ["os.file", "base"];
|
|
||||||
|
|
||||||
readInt=func(fh){
|
|
||||||
line=readFile(fh);
|
|
||||||
line ? [nil] {nil} :: {int(line)}
|
|
||||||
};
|
|
||||||
|
|
||||||
ds={
|
|
||||||
"init":func(filename){
|
|
||||||
fh=openFile(filename);
|
|
||||||
fh ? [nil] {nil} :: { @current=readInt(fh); @prev=@current };
|
|
||||||
fh
|
|
||||||
},
|
|
||||||
"current":func(){
|
|
||||||
prev
|
|
||||||
},
|
|
||||||
"next":func(fh){
|
|
||||||
current ?
|
|
||||||
[nil] {current}
|
|
||||||
:: {@prev=current; @current=readInt(fh) but current}
|
|
||||||
},
|
|
||||||
"clean":func(fh){
|
|
||||||
closeFile(fh)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//;f=$(ds, "int.list")
|
|
||||||
/*
|
|
||||||
;f++
|
|
||||||
;f++
|
|
||||||
;f++
|
|
||||||
*/
|
|
||||||
//;add(f)
|
|
||||||
+64
-1
@@ -4,13 +4,76 @@
|
|||||||
// formatter.go
|
// formatter.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
type FmtOpt uint16
|
import "fmt"
|
||||||
|
|
||||||
|
type FmtOpt uint32 // lower 16 bits hold a bit-mask, higher 16 bits hold an indentation number
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TTY FmtOpt = 1 << iota
|
TTY FmtOpt = 1 << iota
|
||||||
MultiLine
|
MultiLine
|
||||||
|
Truncate
|
||||||
|
Base2
|
||||||
|
Base8
|
||||||
|
Base10
|
||||||
|
Base16
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TruncateEllipsis = "(...)"
|
||||||
|
MinTruncateSize = 10
|
||||||
|
TruncateSize = MinTruncateSize + 15
|
||||||
|
)
|
||||||
|
|
||||||
|
func TruncateString(s string) (trunc string) {
|
||||||
|
finalPart := len(s) - (MinTruncateSize - len(TruncateEllipsis))
|
||||||
|
trunc = s[0:len(s)-MinTruncateSize] + TruncateEllipsis + s[finalPart:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeFormatOptions(flags FmtOpt, indent int) FmtOpt {
|
||||||
|
return FmtOpt(indent<<16) | flags
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFormatFlags(opt FmtOpt) FmtOpt {
|
||||||
|
return opt & 0xFFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFormatIndent(opt FmtOpt) int {
|
||||||
|
return int(opt >> 16)
|
||||||
|
}
|
||||||
|
|
||||||
type Formatter interface {
|
type Formatter interface {
|
||||||
ToString(options FmtOpt) string
|
ToString(options FmtOpt) string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getFormatted(v any, opt FmtOpt) (text string) {
|
||||||
|
if v == nil {
|
||||||
|
text = "(nil)"
|
||||||
|
} else if s, ok := v.(string); ok {
|
||||||
|
text = s
|
||||||
|
} else if formatter, ok := v.(Formatter); ok {
|
||||||
|
text = formatter.ToString(opt)
|
||||||
|
} else {
|
||||||
|
text = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Typer interface {
|
||||||
|
TypeName() string
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
} else if IsFloat(v) {
|
||||||
|
name = "float"
|
||||||
|
} else {
|
||||||
|
name = fmt.Sprintf("%T", v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -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]
|
||||||
|
// }
|
||||||
|
s = strings.TrimSuffix(s, "()")
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// func-builtins.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
func isNilFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
|
||||||
if len(args) == 1 {
|
|
||||||
result = args[0] == nil
|
|
||||||
} else {
|
|
||||||
err = errOneParam(name)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
|
||||||
if len(args) == 1 {
|
|
||||||
switch v := args[0].(type) {
|
|
||||||
case int64:
|
|
||||||
result = v
|
|
||||||
case float64:
|
|
||||||
result = int64(math.Trunc(v))
|
|
||||||
case bool:
|
|
||||||
if v {
|
|
||||||
result = int64(1)
|
|
||||||
} else {
|
|
||||||
result = int64(0)
|
|
||||||
}
|
|
||||||
case string:
|
|
||||||
var i int
|
|
||||||
if i, err = strconv.Atoi(v); err == nil {
|
|
||||||
result = int64(i)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
err = errCantConvert(name, v, "int")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = errOneParam(name)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func ImportBuiltinsFuncs(ctx ExprContext) {
|
|
||||||
ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, -1)
|
|
||||||
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registerImport("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
|
|
||||||
}
|
|
||||||
-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")
|
|
||||||
}
|
|
||||||
-118
@@ -1,118 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// func-string.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// --- Start of function definitions
|
|
||||||
func doJoinStr(funcName string, sep string, it Iterator) (result any, err error) {
|
|
||||||
var sb strings.Builder
|
|
||||||
var v any
|
|
||||||
for v, err = it.Next(); err == nil; v, err = it.Next() {
|
|
||||||
if it.Index() > 0 {
|
|
||||||
sb.WriteString(sep)
|
|
||||||
}
|
|
||||||
if s, ok := v.(string); ok {
|
|
||||||
sb.WriteString(s)
|
|
||||||
} else {
|
|
||||||
err = errExpectedGot(funcName, typeString, v)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err == nil || err == io.EOF {
|
|
||||||
err = nil
|
|
||||||
result = sb.String()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
|
||||||
if len(args) < 1 {
|
|
||||||
return nil, errMissingRequiredParameter(name, paramSeparator)
|
|
||||||
}
|
|
||||||
if sep, ok := args[0].(string); ok {
|
|
||||||
if len(args) == 1 {
|
|
||||||
result = ""
|
|
||||||
} else if len(args) == 2 {
|
|
||||||
if ls, ok := args[1].(*ListType); ok {
|
|
||||||
result, err = doJoinStr(name, sep, NewListIterator(ls, nil))
|
|
||||||
} else if it, ok := args[1].(Iterator); ok {
|
|
||||||
result, err = doJoinStr(name, sep, it)
|
|
||||||
} else {
|
|
||||||
err = errInvalidParameterValue(name, paramParts, args[1])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result, err = doJoinStr(name, sep, NewArrayIterator(args[1:]))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = errWrongParamType(name, paramSeparator, typeString, args[0])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
|
||||||
var start = 0
|
|
||||||
var count = -1
|
|
||||||
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])
|
|
||||||
}
|
|
||||||
if len(args) > 1 {
|
|
||||||
if start, err = toInt(args[1], name+"()"); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(args) > 2 {
|
|
||||||
if count, err = toInt(args[2], name+"()"); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if start < 0 {
|
|
||||||
start = len(source) + start
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if count < 0 {
|
|
||||||
count = len(source) - start
|
|
||||||
}
|
|
||||||
end := min(start+count, len(source))
|
|
||||||
result = source[start:end]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func trimStrFunc(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])
|
|
||||||
}
|
|
||||||
result = strings.TrimSpace(source)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- End of function definitions
|
|
||||||
|
|
||||||
// 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("trimStr", &simpleFunctor{f: trimStrFunc}, 1, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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")
|
|
||||||
}
|
|
||||||
@@ -1,45 +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(`int() requires exactly one param`)},
|
|
||||||
/* 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},
|
|
||||||
}
|
|
||||||
|
|
||||||
// parserTest(t, "Func", inputs[25:26])
|
|
||||||
parserTest(t, "Func", inputs)
|
|
||||||
}
|
|
||||||
+256
@@ -0,0 +1,256 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (functor *baseFunctor) GetDefinitionContext() ExprContext {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 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
|
||||||
|
|
||||||
|
// funcInfo implements ExprFunc
|
||||||
|
type funcInfo struct {
|
||||||
|
name string
|
||||||
|
minArgs int
|
||||||
|
maxArgs int
|
||||||
|
functor Functor
|
||||||
|
formalParams []ExprFuncParam
|
||||||
|
returnType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFuncInfo(name string, functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
|
||||||
|
var minArgs = 0
|
||||||
|
var maxArgs = 0
|
||||||
|
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, formalParams: params,
|
||||||
|
}
|
||||||
|
functor.SetFunc(info)
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// func newUnnamedFuncInfo(functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
|
||||||
|
// return newFuncInfo("unnamed", functor, returnType, params)
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (info *funcInfo) Params() []ExprFuncParam {
|
||||||
|
return info.formalParams
|
||||||
|
}
|
||||||
|
|
||||||
|
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.formalParams != nil {
|
||||||
|
for i, p := range info.formalParams {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) AllocContext(parentCtx ExprContext) (ctx ExprContext) {
|
||||||
|
if defCtx := info.functor.GetDefinitionContext(); defCtx != nil {
|
||||||
|
ctx = defCtx.Clone()
|
||||||
|
ctx.SetParent(defCtx)
|
||||||
|
} else {
|
||||||
|
ctx = parentCtx.Clone()
|
||||||
|
ctx.SetParent(parentCtx)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) PrepareCall(parentCtx ExprContext, name string, varActualParams *[]any) (ctx ExprContext, err error) {
|
||||||
|
passedCount := len(*varActualParams)
|
||||||
|
if info.MinArgs() > passedCount {
|
||||||
|
err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := passedCount; i < len(info.formalParams); i++ {
|
||||||
|
p := info.formalParams[i]
|
||||||
|
if !p.IsDefault() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
*varActualParams = append(*varActualParams, p.DefaultValue())
|
||||||
|
}
|
||||||
|
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(*varActualParams) {
|
||||||
|
err = ErrTooMuchParams(name, info.MaxArgs(), len(*varActualParams))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
ctx = info.AllocContext(parentCtx)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Call a function ---
|
||||||
|
|
||||||
|
func CallFunction(parentCtx ExprContext, name string, actualParams []any) (result any, err error) {
|
||||||
|
if info, exists, _ := GetFuncInfo(parentCtx, name); exists {
|
||||||
|
var ctx ExprContext
|
||||||
|
if ctx, err = info.PrepareCall(parentCtx, name, &actualParams); err == nil {
|
||||||
|
functor := info.Functor()
|
||||||
|
result, err = functor.Invoke(ctx, name, actualParams)
|
||||||
|
exportObjectsToParent(ctx)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("unknown function %s()", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// global-context.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var globalCtx *SimpleStore
|
||||||
|
|
||||||
|
func ImportInContext(name string) (exists bool) {
|
||||||
|
var mod *builtinModule
|
||||||
|
if mod, exists = builtinModuleRegister[name]; exists {
|
||||||
|
mod.importFunc(globalCtx)
|
||||||
|
mod.imported = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ImportInContextByGlobPattern(pattern string) (count int, err error) {
|
||||||
|
var matched bool
|
||||||
|
for name, mod := range builtinModuleRegister {
|
||||||
|
if matched, err = filepath.Match(pattern, name); err == nil {
|
||||||
|
if matched {
|
||||||
|
count++
|
||||||
|
mod.importFunc(globalCtx)
|
||||||
|
mod.imported = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetVar(ctx ExprContext, name string) (value any, exists bool) {
|
||||||
|
if value, exists = ctx.GetVar(name); !exists {
|
||||||
|
value, exists = globalCtx.GetVar(name)
|
||||||
|
}
|
||||||
|
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 len(name) > 0 {
|
||||||
|
if item, exists = GetLocalFuncInfo(ctx, name); exists {
|
||||||
|
ownerCtx = ctx
|
||||||
|
} else if item, exists = globalCtx.GetFuncInfo(name); exists {
|
||||||
|
ownerCtx = globalCtx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func GlobalCtrlSet(name string, newValue any) (currentValue any) {
|
||||||
|
if !strings.HasPrefix(name, "_") {
|
||||||
|
name = "_" + name
|
||||||
|
}
|
||||||
|
currentValue, _ = globalCtx.GetVar(name)
|
||||||
|
|
||||||
|
globalCtx.SetVar(name, newValue)
|
||||||
|
return currentValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func GlobalCtrlGet(name string) (currentValue any) {
|
||||||
|
if !strings.HasPrefix(name, "_") {
|
||||||
|
name = "_" + name
|
||||||
|
}
|
||||||
|
currentValue, _ = globalCtx.GetVar(name)
|
||||||
|
return currentValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func CtrlEnable(ctx ExprContext, name string) (currentStatus bool) {
|
||||||
|
if !strings.HasPrefix(name, "_") {
|
||||||
|
name = "_" + name
|
||||||
|
}
|
||||||
|
if v, exists := ctx.GetVar(name); exists && IsBool(v) {
|
||||||
|
currentStatus, _ = v.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetVar(name, true)
|
||||||
|
return currentStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func CtrlDisable(ctx ExprContext, name string) (currentStatus bool) {
|
||||||
|
if !strings.HasPrefix(name, "_") {
|
||||||
|
name = "_" + name
|
||||||
|
}
|
||||||
|
if v, exists := ctx.GetVar(name); exists && IsBool(v) {
|
||||||
|
currentStatus, _ = v.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetVar(name, false)
|
||||||
|
return currentStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func CtrlIsEnabled(ctx ExprContext, name string) (status bool) {
|
||||||
|
var v any
|
||||||
|
var exists bool
|
||||||
|
|
||||||
|
if !strings.HasPrefix(name, "_") {
|
||||||
|
name = "_" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, exists = ctx.GetVar(name); !exists {
|
||||||
|
v, exists = globalCtx.GetVar(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
if b, ok := v.(bool); ok {
|
||||||
|
status = b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getControlString(name string) (s string, exists bool) {
|
||||||
|
var v any
|
||||||
|
if v, exists = globalCtx.GetVar(name); exists {
|
||||||
|
s, exists = v.(string)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
globalCtx = NewSimpleStore()
|
||||||
|
initDefaultVars(globalCtx)
|
||||||
|
ImportBuiltinsFuncs(globalCtx)
|
||||||
|
}
|
||||||
+9
-6
@@ -16,10 +16,10 @@ func EvalString(ctx ExprContext, source string) (result any, err error) {
|
|||||||
|
|
||||||
r := strings.NewReader(source)
|
r := strings.NewReader(source)
|
||||||
scanner := NewScanner(r, DefaultTranslations())
|
scanner := NewScanner(r, DefaultTranslations())
|
||||||
parser := NewParser(ctx)
|
parser := NewParser()
|
||||||
|
|
||||||
if tree, err = parser.Parse(scanner); err == nil {
|
if tree, err = parser.Parse(scanner); err == nil {
|
||||||
result, err = tree.eval(ctx, true)
|
result, err = tree.Eval(ctx)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -34,12 +34,15 @@ func EvalStringA(source string, args ...Arg) (result any, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func EvalStringV(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 {
|
for _, arg := range args {
|
||||||
if isFunc(arg.Value) {
|
if isFunc(arg.Value) {
|
||||||
if f, ok := arg.Value.(FuncTemplate); ok {
|
if f, ok := arg.Value.(FuncTemplate); ok {
|
||||||
functor := &simpleFunctor{f: f}
|
functor := NewGolangFunctor(f)
|
||||||
ctx.RegisterFunc(arg.Name, functor, 0, -1)
|
// ctx.RegisterFunc(arg.Name, functor, 0, -1)
|
||||||
|
ctx.RegisterFunc(arg.Name, functor, TypeAny, []ExprFuncParam{
|
||||||
|
NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, 0),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("invalid function specification: %q", arg.Name)
|
err = fmt.Errorf("invalid function specification: %q", arg.Name)
|
||||||
}
|
}
|
||||||
@@ -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) {
|
func EvalStream(ctx ExprContext, r io.Reader) (result any, err error) {
|
||||||
var tree *ast
|
var tree *ast
|
||||||
scanner := NewScanner(r, DefaultTranslations())
|
scanner := NewScanner(r, DefaultTranslations())
|
||||||
parser := NewParser(ctx)
|
parser := NewParser()
|
||||||
|
|
||||||
if tree, err = parser.Parse(scanner); err == nil {
|
if tree, err = parser.Parse(scanner); err == nil {
|
||||||
result, err = tree.Eval(ctx)
|
result, err = tree.Eval(ctx)
|
||||||
|
|||||||
+104
@@ -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
|
||||||
|
}
|
||||||
+4
-1
@@ -19,9 +19,12 @@ const (
|
|||||||
currentName = "current"
|
currentName = "current"
|
||||||
indexName = "index"
|
indexName = "index"
|
||||||
countName = "count"
|
countName = "count"
|
||||||
|
filterName = "filter"
|
||||||
|
mapName = "map"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Iterator interface {
|
type Iterator interface {
|
||||||
|
Typer
|
||||||
Next() (item any, err error) // must return io.EOF after the last item
|
Next() (item any, err error) // must return io.EOF after the last item
|
||||||
Current() (item any, err error)
|
Current() (item any, err error)
|
||||||
Index() int
|
Index() int
|
||||||
@@ -34,7 +37,7 @@ type ExtIterator interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func errNoOperation(name string) error {
|
func errNoOperation(name string) error {
|
||||||
return fmt.Errorf("no %q function defined in the data-source", name)
|
return fmt.Errorf("no %s() function defined in the data-source", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func errInvalidDataSource() error {
|
func errInvalidDataSource() error {
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
// iter-list.go
|
// list-iterator.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -26,21 +26,21 @@ func NewListIterator(list *ListType, args []any) (it *ListIterator) {
|
|||||||
}
|
}
|
||||||
it = &ListIterator{a: list, count: 0, index: -1, start: 0, stop: listLen - 1, step: 1}
|
it = &ListIterator{a: list, count: 0, index: -1, start: 0, stop: listLen - 1, step: 1}
|
||||||
if argc >= 1 {
|
if argc >= 1 {
|
||||||
if i, err := toInt(args[0], "start index"); err == nil {
|
if i, err := ToGoInt(args[0], "start index"); err == nil {
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
i = listLen + i
|
i = listLen + i
|
||||||
}
|
}
|
||||||
it.start = i
|
it.start = i
|
||||||
}
|
}
|
||||||
if argc >= 2 {
|
if argc >= 2 {
|
||||||
if i, err := toInt(args[1], "stop index"); err == nil {
|
if i, err := ToGoInt(args[1], "stop index"); err == nil {
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
i = listLen + i
|
i = listLen + i
|
||||||
}
|
}
|
||||||
it.stop = i
|
it.stop = i
|
||||||
}
|
}
|
||||||
if argc >= 3 {
|
if argc >= 3 {
|
||||||
if i, err := toInt(args[2], "step"); err == nil {
|
if i, err := ToGoInt(args[2], "step"); err == nil {
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
i = -i
|
i = -i
|
||||||
}
|
}
|
||||||
@@ -85,17 +85,25 @@ func (it *ListIterator) String() string {
|
|||||||
return fmt.Sprintf("$(#%d)", l)
|
return fmt.Sprintf("$(#%d)", l)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) TypeName() string {
|
||||||
|
return "ListIterator"
|
||||||
|
}
|
||||||
|
|
||||||
func (it *ListIterator) HasOperation(name string) bool {
|
func (it *ListIterator) HasOperation(name string) bool {
|
||||||
yes := name == resetName || name == indexName || name == countName
|
yes := name == nextName || name == resetName || name == indexName || name == countName || name == currentName
|
||||||
return yes
|
return yes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *ListIterator) CallOperation(name string, args []any) (v any, err error) {
|
func (it *ListIterator) CallOperation(name string, args []any) (v any, err error) {
|
||||||
switch name {
|
switch name {
|
||||||
|
case nextName:
|
||||||
|
v, err = it.Next()
|
||||||
case resetName:
|
case resetName:
|
||||||
v, err = it.Reset()
|
v, err = it.Reset()
|
||||||
case indexName:
|
case indexName:
|
||||||
v = int64(it.Index())
|
v = int64(it.Index())
|
||||||
|
case currentName:
|
||||||
|
v, err = it.Current()
|
||||||
case countName:
|
case countName:
|
||||||
v = it.count
|
v = it.count
|
||||||
default:
|
default:
|
||||||
@@ -106,11 +114,20 @@ func (it *ListIterator) CallOperation(name string, args []any) (v any, err error
|
|||||||
|
|
||||||
func (it *ListIterator) Current() (item any, err error) {
|
func (it *ListIterator) Current() (item any, err error) {
|
||||||
a := *(it.a)
|
a := *(it.a)
|
||||||
if it.index >= 0 && it.index <= it.stop {
|
if it.start <= it.stop {
|
||||||
|
if it.stop < len(a) && it.index >= it.start && it.index <= it.stop {
|
||||||
item = a[it.index]
|
item = a[it.index]
|
||||||
} else {
|
} else {
|
||||||
err = io.EOF
|
err = io.EOF
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if it.start < len(a) && it.index >= it.stop && it.index <= it.start {
|
||||||
|
item = a[it.index]
|
||||||
|
} else {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,6 +144,6 @@ func (it *ListIterator) Index() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (it *ListIterator) Reset() (bool, error) {
|
func (it *ListIterator) Reset() (bool, error) {
|
||||||
it.index = it.start
|
it.index = it.start - it.step
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
+195
@@ -0,0 +1,195 @@
|
|||||||
|
// 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
|
||||||
|
// }
|
||||||
|
copy(ls, listAny)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// module-register.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// operand-const.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
// -------- const term
|
|
||||||
func newConstTerm(tk *Token) *term {
|
|
||||||
return &term{
|
|
||||||
tk: *tk,
|
|
||||||
parent: nil,
|
|
||||||
children: nil,
|
|
||||||
position: posLeaf,
|
|
||||||
priority: priValue,
|
|
||||||
evalFunc: evalConst,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- eval func
|
|
||||||
func evalConst(ctx ExprContext, self *term) (v any, err error) {
|
|
||||||
v = self.tk.Value
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// init
|
|
||||||
func init() {
|
|
||||||
registerTermConstructor(SymString, newConstTerm)
|
|
||||||
registerTermConstructor(SymInteger, newConstTerm)
|
|
||||||
registerTermConstructor(SymFloat, newConstTerm)
|
|
||||||
registerTermConstructor(SymBool, newConstTerm)
|
|
||||||
registerTermConstructor(SymKwNil, newConstTerm)
|
|
||||||
}
|
|
||||||
+5
-4
@@ -4,6 +4,7 @@
|
|||||||
// operand-dict.go
|
// operand-dict.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
|
|
||||||
// -------- dict term
|
// -------- dict term
|
||||||
func newDictTerm(args map[any]*term) *term {
|
func newDictTerm(args map[any]*term) *term {
|
||||||
return &term{
|
return &term{
|
||||||
@@ -17,9 +18,9 @@ func newDictTerm(args map[any]*term) *term {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------- dict func
|
// -------- dict func
|
||||||
func evalDict(ctx ExprContext, self *term) (v any, err error) {
|
func evalDict(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
dict, _ := self.value().(map[any]*term)
|
dict, _ := opTerm.value().(map[any]*term)
|
||||||
items := make(map[any]any, len(dict))
|
items := make(DictType, len(dict))
|
||||||
for key, tree := range dict {
|
for key, tree := range dict {
|
||||||
var param any
|
var param any
|
||||||
if param, err = tree.compute(ctx); err != nil {
|
if param, err = tree.compute(ctx); err != nil {
|
||||||
@@ -28,7 +29,7 @@ func evalDict(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
items[key] = param
|
items[key] = param
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
v = items
|
v = &items
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-5
@@ -7,7 +7,8 @@ package expr
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// -------- expr term
|
// -------- expr term
|
||||||
func newExprTerm(tk *Token) *term {
|
func newExprTerm(root *term) *term {
|
||||||
|
tk := NewValueToken(root.tk.row, root.tk.col, SymExpression, root.source(), root)
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
parent: nil,
|
parent: nil,
|
||||||
@@ -19,11 +20,11 @@ func newExprTerm(tk *Token) *term {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------- eval expr
|
// -------- eval expr
|
||||||
func evalExpr(ctx ExprContext, self *term) (v any, err error) {
|
func evalExpr(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
if expr, ok := self.value().(Expr); ok {
|
if expr, ok := opTerm.value().(*term); ok {
|
||||||
v, err = expr.eval(ctx, false)
|
v, err = expr.compute(ctx)
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("expression expected, got %T", self.value())
|
err = fmt.Errorf("expression expected, got %T", opTerm.value())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-56
@@ -6,7 +6,6 @@ package expr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// -------- function call term
|
// -------- function call term
|
||||||
@@ -22,38 +21,19 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------- eval func call
|
// -------- eval func call
|
||||||
func checkFunctionCall(ctx ExprContext, name string, params []any) (err error) {
|
func evalFuncCall(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
if info, exists := ctx.GetFuncInfo(name); exists {
|
name, _ := opTerm.tk.Value.(string)
|
||||||
if info.MinArgs() > len(params) {
|
params := make([]any, len(opTerm.children), len(opTerm.children)+5)
|
||||||
err = fmt.Errorf("too few params -- expected %d, got %d", info.MinArgs(), len(params))
|
for i, tree := range opTerm.children {
|
||||||
}
|
|
||||||
if info.MaxArgs() >= 0 && info.MaxArgs() < len(params) {
|
|
||||||
err = fmt.Errorf("too much params -- expected %d, got %d", info.MaxArgs(), len(params))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("unknown function %s()", name)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
|
|
||||||
ctx := cloneContext(parentCtx)
|
|
||||||
name, _ := self.tk.Value.(string)
|
|
||||||
// fmt.Printf("Call %s(), context: %p\n", name, ctx)
|
|
||||||
params := make([]any, len(self.children))
|
|
||||||
for i, tree := range self.children {
|
|
||||||
var param any
|
var param any
|
||||||
if param, err = tree.compute(ctx); err != nil {
|
if param, err = tree.compute(ctx); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
params[i] = param
|
params[i] = param
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if err = checkFunctionCall(ctx, name, params); err == nil {
|
v, err = CallFunction(ctx, name, params)
|
||||||
if v, err = ctx.Call(name, params); err == nil {
|
|
||||||
exportObjects(parentCtx, ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -71,40 +51,23 @@ func newFuncDefTerm(tk *Token, args []*term) *term {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------- eval func def
|
// -------- eval func def
|
||||||
// TODO
|
func evalFuncDef(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
type funcDefFunctor struct {
|
bodySpec := opTerm.value()
|
||||||
params []string
|
if expr, ok := bodySpec.(*ast); ok {
|
||||||
expr Expr
|
paramList := make([]ExprFuncParam, 0, len(opTerm.children))
|
||||||
}
|
for _, param := range opTerm.children {
|
||||||
|
var defValue any
|
||||||
func (functor *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
flags := paramFlags(0)
|
||||||
for i, p := range functor.params {
|
if len(param.children) > 0 {
|
||||||
if i < len(args) {
|
flags |= PfDefault
|
||||||
arg := args[i]
|
if defValue, err = param.children[0].compute(ctx); err != nil {
|
||||||
if functor, ok := arg.(Functor); ok {
|
|
||||||
ctx.RegisterFunc(p, functor, 0, -1)
|
|
||||||
} else {
|
|
||||||
ctx.setVar(p, arg)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctx.setVar(p, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result, err = functor.expr.eval(ctx, false)
|
|
||||||
return
|
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))
|
|
||||||
for _, param := range self.children {
|
|
||||||
paramList = append(paramList, param.source())
|
|
||||||
}
|
}
|
||||||
v = &funcDefFunctor{
|
info := NewFuncParamFlagDef(param.source(), flags, defValue)
|
||||||
params: paramList,
|
paramList = append(paramList, info)
|
||||||
expr: expr,
|
|
||||||
}
|
}
|
||||||
|
v = newExprFunctor(expr, paramList, ctx)
|
||||||
} else {
|
} else {
|
||||||
err = errors.New("invalid function definition: the body specification must be an expression")
|
err = errors.New("invalid function definition: the body specification must be an expression")
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-55
@@ -5,28 +5,27 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// -------- iterator term
|
// -------- iterator term
|
||||||
|
|
||||||
func newDsIteratorTerm(tk *Token, dsTerm *term, args []*term) *term {
|
// func newDsIteratorTerm(tk *Token, dsTerm *term, args []*term) *term {
|
||||||
tk.Sym = SymIterator
|
// tk.Sym = SymIterator
|
||||||
|
|
||||||
children := make([]*term, 0, 1+len(args))
|
// children := make([]*term, 0, 1+len(args))
|
||||||
children = append(children, dsTerm)
|
// children = append(children, dsTerm)
|
||||||
children = append(children, args...)
|
// children = append(children, args...)
|
||||||
return &term{
|
// return &term{
|
||||||
tk: *tk,
|
// tk: *tk,
|
||||||
parent: nil,
|
// parent: nil,
|
||||||
children: children,
|
// children: children,
|
||||||
position: posLeaf,
|
// position: posLeaf,
|
||||||
priority: priValue,
|
// priority: priValue,
|
||||||
evalFunc: evalIterator,
|
// evalFunc: evalIterator,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func newIteratorTerm(tk *Token, args []*term) *term {
|
func newIteratorTerm(tk *Token, args []*term) *term {
|
||||||
tk.Sym = SymIterator
|
tk.Sym = SymIterator
|
||||||
@@ -42,9 +41,9 @@ func newIteratorTerm(tk *Token, args []*term) *term {
|
|||||||
|
|
||||||
// -------- eval iterator
|
// -------- eval iterator
|
||||||
|
|
||||||
func evalTermArray(ctx ExprContext, a []*term) (values []any, err error) {
|
func evalTermArray(ctx ExprContext, terms []*term) (values []any, err error) {
|
||||||
values = make([]any, len(a))
|
values = make([]any, len(terms))
|
||||||
for i, t := range a {
|
for i, t := range terms {
|
||||||
var value any
|
var value any
|
||||||
if value, err = t.compute(ctx); err == nil {
|
if value, err = t.compute(ctx); err == nil {
|
||||||
values[i] = value
|
values[i] = value
|
||||||
@@ -55,25 +54,25 @@ func evalTermArray(ctx ExprContext, a []*term) (values []any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalFirstChild(ctx ExprContext, self *term) (value any, err error) {
|
func evalFirstChild(ctx ExprContext, iteratorTerm *term) (value any, err error) {
|
||||||
if len(self.children) < 1 || self.children[0] == nil {
|
if len(iteratorTerm.children) < 1 || iteratorTerm.children[0] == nil {
|
||||||
err = self.Errorf("missing the data-source parameter")
|
err = iteratorTerm.Errorf("missing the data-source parameter")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
value, err = self.children[0].compute(ctx)
|
value, err = iteratorTerm.children[0].compute(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map[string]Functor, err error) {
|
func getDataSourceDict(iteratorTerm *term, firstChildValue any) (ds map[string]Functor, err error) {
|
||||||
if dictAny, ok := firstChildValue.(map[any]any); ok {
|
if dictAny, ok := firstChildValue.(*DictType); ok {
|
||||||
requiredFields := []string{currentName, nextName}
|
requiredFields := []string{currentName, nextName}
|
||||||
fieldsMask := 0b11
|
fieldsMask := 0b11
|
||||||
foundFields := 0
|
foundFields := 0
|
||||||
ds = make(map[string]Functor)
|
ds = make(map[string]Functor)
|
||||||
for keyAny, item := range dictAny {
|
for keyAny, item := range *dictAny {
|
||||||
if key, ok := keyAny.(string); ok {
|
if key, ok := keyAny.(string); ok {
|
||||||
if functor, ok := item.(*funcDefFunctor); ok {
|
if functor, ok := item.(Functor); ok {
|
||||||
ds[key] = functor
|
ds[key] = functor
|
||||||
if index := slices.Index(requiredFields, key); index >= 0 {
|
if index := slices.Index(requiredFields, key); index >= 0 {
|
||||||
foundFields |= 1 << index
|
foundFields |= 1 << index
|
||||||
@@ -89,21 +88,22 @@ func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map
|
|||||||
missingFields = append(missingFields, field)
|
missingFields = append(missingFields, field)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = fmt.Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", "))
|
// err = fmt.Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", "))
|
||||||
|
err = iteratorTerm.children[0].Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalIterator(ctx ExprContext, self *term) (v any, err error) {
|
func evalIterator(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
var firstChildValue any
|
var firstChildValue any
|
||||||
var ds map[string]Functor
|
var ds map[string]Functor
|
||||||
|
|
||||||
if firstChildValue, err = evalFirstChild(ctx, self); err != nil {
|
if firstChildValue, err = evalFirstChild(ctx, opTerm); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ds, err = getDataSourceDict(ctx, self, firstChildValue); err != nil {
|
if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,8 +111,8 @@ func evalIterator(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
dc := newDataCursor(ctx, ds)
|
dc := newDataCursor(ctx, ds)
|
||||||
if initFunc, exists := ds[initName]; exists && initFunc != nil {
|
if initFunc, exists := ds[initName]; exists && initFunc != nil {
|
||||||
var args []any
|
var args []any
|
||||||
if len(self.children) > 1 {
|
if len(opTerm.children) > 1 {
|
||||||
if args, err = evalTermArray(ctx, self.children[1:]); err != nil {
|
if args, err = evalTermArray(ctx, opTerm.children[1:]); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -126,43 +126,26 @@ func evalIterator(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
exportObjects(dc.ctx, initCtx)
|
exportObjects(dc.ctx, initCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
dc.nextFunc, _ = ds[nextName]
|
dc.nextFunc = ds[nextName]
|
||||||
dc.currentFunc, _ = ds[currentName]
|
dc.currentFunc = ds[currentName]
|
||||||
dc.cleanFunc, _ = ds[cleanName]
|
dc.cleanFunc = ds[cleanName]
|
||||||
dc.resetFunc, _ = ds[resetName]
|
dc.resetFunc = ds[resetName]
|
||||||
|
|
||||||
v = dc
|
v = dc
|
||||||
} else if list, ok := firstChildValue.(*ListType); ok {
|
} else if list, ok := firstChildValue.(*ListType); ok {
|
||||||
var args []any
|
var args []any
|
||||||
if args, err = evalSibling(ctx, self.children, nil); err == nil {
|
if args, err = evalSibling(ctx, opTerm.children, nil); err == nil {
|
||||||
v = NewListIterator(list, args)
|
v = NewListIterator(list, args)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var list []any
|
var list []any
|
||||||
if list, err = evalSibling(ctx, self.children, firstChildValue); err == nil {
|
if list, err = evalSibling(ctx, opTerm.children, firstChildValue); err == nil {
|
||||||
v = NewArrayIterator(list)
|
v = NewArrayIterator(list)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// func evalChildren(ctx ExprContext, terms []*term, firstChildValue any) (list *ListType, err error) {
|
|
||||||
// items := make(ListType, len(terms))
|
|
||||||
// for i, tree := range terms {
|
|
||||||
// var param any
|
|
||||||
// if i == 0 && firstChildValue != nil {
|
|
||||||
// param = firstChildValue
|
|
||||||
// } else if param, err = tree.compute(ctx); err != nil {
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// items[i] = param
|
|
||||||
// }
|
|
||||||
// if err == nil {
|
|
||||||
// list = &items
|
|
||||||
// }
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
func evalSibling(ctx ExprContext, terms []*term, firstChildValue any) (list []any, err error) {
|
func evalSibling(ctx ExprContext, terms []*term, firstChildValue any) (list []any, err error) {
|
||||||
items := make([]any, 0, len(terms))
|
items := make([]any, 0, len(terms))
|
||||||
for i, tree := range terms {
|
for i, tree := range terms {
|
||||||
|
|||||||
+5
-47
@@ -4,56 +4,14 @@
|
|||||||
// operand-list.go
|
// operand-list.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ListType []any
|
|
||||||
|
|
||||||
func (ls *ListType) ToString(opt FmtOpt) 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(']')
|
|
||||||
return sb.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ls *ListType) String() string {
|
|
||||||
return ls.ToString(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- list term
|
// -------- list term
|
||||||
func newListTermA(args ...*term) *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{
|
return &term{
|
||||||
tk: *NewValueToken(0, 0, SymList, "[]", args),
|
tk: *NewValueToken(row, col, SymList, "[]", args),
|
||||||
parent: nil,
|
parent: nil,
|
||||||
children: nil,
|
children: nil,
|
||||||
position: posLeaf,
|
position: posLeaf,
|
||||||
@@ -63,8 +21,8 @@ func newListTerm(args []*term) *term {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------- list func
|
// -------- list func
|
||||||
func evalList(ctx ExprContext, self *term) (v any, err error) {
|
func evalList(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
list, _ := self.value().([]*term)
|
list, _ := opTerm.value().([]*term)
|
||||||
items := make(ListType, len(list))
|
items := make(ListType, len(list))
|
||||||
for i, tree := range list {
|
for i, tree := range list {
|
||||||
var param any
|
var param any
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operand-literal.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
// -------- literal term
|
||||||
|
func newLiteralTerm(tk *Token) *term {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
parent: nil,
|
||||||
|
children: nil,
|
||||||
|
position: posLeaf,
|
||||||
|
priority: priValue,
|
||||||
|
evalFunc: evalLiteral,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- eval func
|
||||||
|
func evalLiteral(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
v = opTerm.tk.Value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymString, newLiteralTerm)
|
||||||
|
registerTermConstructor(SymInteger, newLiteralTerm)
|
||||||
|
registerTermConstructor(SymFloat, newLiteralTerm)
|
||||||
|
registerTermConstructor(SymFraction, newLiteralTerm)
|
||||||
|
registerTermConstructor(SymBool, newLiteralTerm)
|
||||||
|
registerTermConstructor(SymKwNil, newLiteralTerm)
|
||||||
|
}
|
||||||
@@ -41,10 +41,10 @@ func newSelectorCaseTerm(row, col int, filterList *term, caseExpr Expr) *term {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------- eval selector case
|
// -------- eval selector case
|
||||||
func evalSelectorCase(ctx ExprContext, self *term) (v any, err error) {
|
func evalSelectorCase(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
var ok bool
|
var ok bool
|
||||||
if v, ok = self.value().(*selectorCase); !ok {
|
if v, ok = opTerm.value().(*selectorCase); !ok {
|
||||||
err = fmt.Errorf("selector-case expected, got %T", self.value())
|
err = fmt.Errorf("selector-case expected, got %T", opTerm.value())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-7
@@ -8,24 +8,24 @@ import "fmt"
|
|||||||
|
|
||||||
// -------- variable term
|
// -------- variable term
|
||||||
func newVarTerm(tk *Token) *term {
|
func newVarTerm(tk *Token) *term {
|
||||||
return &term{
|
t := &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classVar,
|
|
||||||
// kind: kindUnknown,
|
|
||||||
parent: nil,
|
parent: nil,
|
||||||
children: nil,
|
children: nil,
|
||||||
position: posLeaf,
|
position: posLeaf,
|
||||||
priority: priValue,
|
priority: priValue,
|
||||||
evalFunc: evalVar,
|
evalFunc: evalVar,
|
||||||
}
|
}
|
||||||
|
t.tk.Sym = SymVariable
|
||||||
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- eval func
|
// -------- eval func
|
||||||
func evalVar(ctx ExprContext, self *term) (v any, err error) {
|
func evalVar(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
var exists bool
|
var exists bool
|
||||||
name := self.source()
|
name := opTerm.source()
|
||||||
if v, exists = ctx.GetVar(name); !exists {
|
if v, exists = GetVar(ctx, name); !exists {
|
||||||
if info, exists := ctx.GetFuncInfo(name); exists {
|
if info, exists, _ := GetFuncInfo(ctx, name); exists {
|
||||||
v = info.Functor()
|
v = info.Functor()
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("undefined variable or function %q", name)
|
err = fmt.Errorf("undefined variable or function %q", name)
|
||||||
|
|||||||
+72
-17
@@ -1,5 +1,5 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
// All rights reserved.
|
// All rights reserightChilded.
|
||||||
|
|
||||||
// operator-assign.go
|
// operator-assign.go
|
||||||
package expr
|
package expr
|
||||||
@@ -16,30 +16,85 @@ func newAssignTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalAssign(ctx ExprContext, self *term) (v any, err error) {
|
func assignCollectionItem(ctx ExprContext, collectionTerm, keyListTerm *term, value any) (err error) {
|
||||||
if err = self.checkOperands(); err != nil {
|
var collectionValue, keyListValue, keyValue any
|
||||||
|
var keyList *ListType
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if collectionValue, err = collectionTerm.compute(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
leftTerm := self.children[0]
|
if keyListValue, err = keyListTerm.compute(ctx); err != nil {
|
||||||
if leftTerm.tk.Sym != SymIdentifier {
|
return
|
||||||
err = leftTerm.tk.Errorf("left operand of %q must be a variable", self.tk.source)
|
} 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, err = self.children[1].compute(ctx); err == nil {
|
switch collection := collectionValue.(type) {
|
||||||
if functor, ok := v.(Functor); ok {
|
case *ListType:
|
||||||
var minArgs, maxArgs int = 0, 0
|
if index, ok := keyValue.(int64); ok {
|
||||||
|
err = collection.setItem(index, value)
|
||||||
if funcDef, ok := functor.(*funcDefFunctor); ok {
|
|
||||||
l := len(funcDef.params)
|
|
||||||
minArgs = l
|
|
||||||
maxArgs = l
|
|
||||||
}
|
|
||||||
ctx.RegisterFunc(leftTerm.source(), functor, minArgs, maxArgs)
|
|
||||||
} else {
|
} else {
|
||||||
ctx.setVar(leftTerm.source(), v)
|
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, opTerm *term) (v any, err error) {
|
||||||
|
if err = opTerm.checkOperands(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
leftTerm := opTerm.children[0]
|
||||||
|
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", opTerm.tk.source)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rightChild := opTerm.children[1]
|
||||||
|
|
||||||
|
if v, err = rightChild.compute(ctx); err == nil {
|
||||||
|
if functor, ok := v.(Functor); ok {
|
||||||
|
if leftSym == SymVariable {
|
||||||
|
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 })
|
||||||
|
|
||||||
|
ctx.RegisterFunc(leftTerm.source(), functor, TypeAny, paramSpecs)
|
||||||
|
} else {
|
||||||
|
err = opTerm.Errorf("unknown function %s()", rightChild.source())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = assignValue(ctx, leftTerm, v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = assignValue(ctx, leftTerm, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
v = nil
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-20
@@ -18,17 +18,17 @@ func newNotTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalNot(ctx ExprContext, self *term) (v any, err error) {
|
func evalNot(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
var rightValue any
|
var rightValue any
|
||||||
|
|
||||||
if rightValue, err = self.evalPrefix(ctx); err != nil {
|
if rightValue, err = opTerm.evalPrefix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if b, ok := toBool(rightValue); ok {
|
if b, ok := ToBool(rightValue); ok {
|
||||||
v = !b
|
v = !b
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleType(rightValue)
|
err = opTerm.errIncompatibleType(rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -38,8 +38,6 @@ func evalNot(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
func newAndTerm(tk *Token) (inst *term) {
|
func newAndTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindBool,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priAnd,
|
priority: priAnd,
|
||||||
@@ -48,7 +46,7 @@ func newAndTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func evalAnd(ctx ExprContext, self *term) (v any, err error) {
|
func evalAnd(ctx ExprContext, self *term) (v any, err error) {
|
||||||
if isEnabled(ctx, ControlBoolShortcut) {
|
if CtrlIsEnabled(ctx, ControlBoolShortcut) {
|
||||||
v, err = evalAndWithShortcut(ctx, self)
|
v, err = evalAndWithShortcut(ctx, self)
|
||||||
} else {
|
} else {
|
||||||
v, err = evalAndWithoutShortcut(ctx, self)
|
v, err = evalAndWithoutShortcut(ctx, self)
|
||||||
@@ -65,8 +63,8 @@ func evalAndWithoutShortcut(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
leftBool, lok = toBool(leftValue)
|
leftBool, lok = ToBool(leftValue)
|
||||||
rightBool, rok = toBool(rightValue)
|
rightBool, rok = ToBool(rightValue)
|
||||||
|
|
||||||
if lok && rok {
|
if lok && rok {
|
||||||
v = leftBool && rightBool
|
v = leftBool && rightBool
|
||||||
@@ -87,13 +85,13 @@ func evalAndWithShortcut(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if leftBool, lok := toBool(leftValue); !lok {
|
if leftBool, lok := ToBool(leftValue); !lok {
|
||||||
err = fmt.Errorf("got %T as left operand type of 'and' operator, it must be bool", leftBool)
|
err = fmt.Errorf("got %s as left operand type of 'AND' operator, it must be bool", TypeName(leftValue))
|
||||||
return
|
return
|
||||||
} else if !leftBool {
|
} else if !leftBool {
|
||||||
v = false
|
v = false
|
||||||
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
||||||
if rightBool, rok := toBool(rightValue); rok {
|
if rightBool, rok := ToBool(rightValue); rok {
|
||||||
v = rightBool
|
v = rightBool
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
@@ -107,8 +105,6 @@ func evalAndWithShortcut(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
func newOrTerm(tk *Token) (inst *term) {
|
func newOrTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindBool,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priOr,
|
priority: priOr,
|
||||||
@@ -117,7 +113,7 @@ func newOrTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func evalOr(ctx ExprContext, self *term) (v any, err error) {
|
func evalOr(ctx ExprContext, self *term) (v any, err error) {
|
||||||
if isEnabled(ctx, ControlBoolShortcut) {
|
if CtrlIsEnabled(ctx, ControlBoolShortcut) {
|
||||||
v, err = evalOrWithShortcut(ctx, self)
|
v, err = evalOrWithShortcut(ctx, self)
|
||||||
} else {
|
} else {
|
||||||
v, err = evalOrWithoutShortcut(ctx, self)
|
v, err = evalOrWithoutShortcut(ctx, self)
|
||||||
@@ -134,8 +130,8 @@ func evalOrWithoutShortcut(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
leftBool, lok = toBool(leftValue)
|
leftBool, lok = ToBool(leftValue)
|
||||||
rightBool, rok = toBool(rightValue)
|
rightBool, rok = ToBool(rightValue)
|
||||||
|
|
||||||
if lok && rok {
|
if lok && rok {
|
||||||
v = leftBool || rightBool
|
v = leftBool || rightBool
|
||||||
@@ -156,13 +152,13 @@ func evalOrWithShortcut(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if leftBool, lok := toBool(leftValue); !lok {
|
if leftBool, lok := ToBool(leftValue); !lok {
|
||||||
err = fmt.Errorf("got %T as left operand type of 'or' operator, it must be bool", leftBool)
|
err = fmt.Errorf("got %s as left operand type of 'OR' operator, it must be bool", TypeName(leftValue))
|
||||||
return
|
return
|
||||||
} else if leftBool {
|
} else if leftBool {
|
||||||
v = true
|
v = true
|
||||||
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
||||||
if rightBool, rok := toBool(rightValue); rok {
|
if rightBool, rok := ToBool(rightValue); rok {
|
||||||
v = rightBool
|
v = rightBool
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
|||||||
+7
-7
@@ -18,30 +18,30 @@ func newBuiltinTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
|
func evalBuiltin(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
var childValue any
|
var childValue any
|
||||||
|
|
||||||
if childValue, err = self.evalPrefix(ctx); err != nil {
|
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
count := 0
|
count := 0
|
||||||
if isString(childValue) {
|
if IsString(childValue) {
|
||||||
module, _ := childValue.(string)
|
module, _ := childValue.(string)
|
||||||
count, err = ImportInContextByGlobPattern(ctx, module)
|
count, err = ImportInContextByGlobPattern(module)
|
||||||
} else {
|
} else {
|
||||||
var moduleSpec any
|
var moduleSpec any
|
||||||
it := NewAnyIterator(childValue)
|
it := NewAnyIterator(childValue)
|
||||||
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
|
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
|
||||||
if module, ok := moduleSpec.(string); ok {
|
if module, ok := moduleSpec.(string); ok {
|
||||||
if ImportInContext(ctx, module) {
|
if ImportInContext(module) {
|
||||||
count++
|
count++
|
||||||
} else {
|
} else {
|
||||||
err = self.Errorf("unknown module %q", module)
|
err = opTerm.Errorf("unknown builtin module %q", module)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.Errorf("expected string at item nr %d, got %T", it.Index()+1, moduleSpec)
|
err = opTerm.Errorf("expected string at item nr %d, got %s", it.Index()+1, TypeName(moduleSpec))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -16,8 +16,8 @@ func newButTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalBut(ctx ExprContext, self *term) (v any, err error) {
|
func evalBut(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
_, v, err = self.evalInfix(ctx)
|
_, v, err = opTerm.evalInfix(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// operator-coalesce.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
//-------- null coalesce term
|
|
||||||
|
|
||||||
func newNullCoalesceTerm(tk *Token) (inst *term) {
|
|
||||||
return &term{
|
|
||||||
tk: *tk,
|
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindUnknown,
|
|
||||||
children: make([]*term, 0, 2),
|
|
||||||
position: posInfix,
|
|
||||||
priority: priCoalesce,
|
|
||||||
evalFunc: evalNullCoalesce,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func evalNullCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
|
||||||
var rightValue any
|
|
||||||
|
|
||||||
if err = self.checkOperands(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
leftTerm := self.children[0]
|
|
||||||
if leftTerm.tk.Sym != SymIdentifier {
|
|
||||||
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
|
|
||||||
v = leftValue
|
|
||||||
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
|
|
||||||
// if _, ok := rightValue.(Functor); ok {
|
|
||||||
// err = errCoalesceNoFunc(self.children[1])
|
|
||||||
// } else {
|
|
||||||
v = rightValue
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//-------- coalesce assign term
|
|
||||||
|
|
||||||
func newCoalesceAssignTerm(tk *Token) (inst *term) {
|
|
||||||
return &term{
|
|
||||||
tk: *tk,
|
|
||||||
children: make([]*term, 0, 2),
|
|
||||||
position: posInfix,
|
|
||||||
priority: priCoalesce,
|
|
||||||
evalFunc: evalAssignCoalesce,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
|
||||||
var rightValue any
|
|
||||||
|
|
||||||
if err = self.checkOperands(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
leftTerm := self.children[0]
|
|
||||||
if leftTerm.tk.Sym != SymIdentifier {
|
|
||||||
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
|
|
||||||
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)
|
|
||||||
} else {
|
|
||||||
v = rightValue
|
|
||||||
ctx.setVar(leftTerm.source(), rightValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// utils
|
|
||||||
// func errCoalesceNoFunc(t *term) error {
|
|
||||||
// return t.Errorf("the right operand of a coalescing operation cannot be a function definition")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// init
|
|
||||||
func init() {
|
|
||||||
registerTermConstructor(SymDoubleQuestion, newNullCoalesceTerm)
|
|
||||||
registerTermConstructor(SymQuestionEqual, newCoalesceAssignTerm)
|
|
||||||
}
|
|
||||||
+11
-8
@@ -16,25 +16,28 @@ func newContextTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
|
func evalContextValue(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
var childValue any
|
var childValue any
|
||||||
|
|
||||||
var sourceCtx ExprContext
|
var sourceCtx ExprContext
|
||||||
if len(self.children) == 0 {
|
if opTerm.children == nil || len(opTerm.children) == 0 {
|
||||||
sourceCtx = ctx
|
sourceCtx = ctx
|
||||||
} else if childValue, err = self.evalPrefix(ctx); err == nil {
|
} else if opTerm.children[0].symbol() == SymVariable && opTerm.children[0].source() == "global" {
|
||||||
|
sourceCtx = globalCtx
|
||||||
|
} else if childValue, err = opTerm.evalPrefix(ctx); err == nil {
|
||||||
if dc, ok := childValue.(*dataCursor); ok {
|
if dc, ok := childValue.(*dataCursor); ok {
|
||||||
sourceCtx = dc.ctx
|
sourceCtx = dc.ctx
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if sourceCtx != nil {
|
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)
|
v = formatter.ToString(0)
|
||||||
} else {
|
} else {
|
||||||
keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
|
// keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
|
||||||
|
keys := sourceCtx.EnumVars(nil)
|
||||||
d := make(map[string]any)
|
d := make(map[string]any)
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
d[key], _ = sourceCtx.GetVar(key)
|
d[key], _ = sourceCtx.GetVar(key)
|
||||||
@@ -46,7 +49,7 @@ func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
v = d
|
v = d
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleType(childValue)
|
err = opTerm.errIncompatibleType(childValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -16,8 +16,8 @@ func newExportAllTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalExportAll(ctx ExprContext, self *term) (v any, err error) {
|
func evalExportAll(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
enable(ctx, control_export_all)
|
CtrlEnable(ctx, control_export_all)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-default.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
//-------- default term
|
||||||
|
|
||||||
|
func newDefaultTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priDefault,
|
||||||
|
evalFunc: evalDefault,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalDefault(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
var rightValue any
|
||||||
|
|
||||||
|
if err = opTerm.checkOperands(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
leftTerm := opTerm.children[0]
|
||||||
|
if leftTerm.tk.Sym != SymVariable {
|
||||||
|
// err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
||||||
|
err = ErrLeftOperandMustBeVariable(leftTerm, opTerm)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
|
||||||
|
v = leftValue
|
||||||
|
} else if rightValue, err = opTerm.children[1].compute(ctx); err == nil {
|
||||||
|
v = rightValue
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------- alternate term
|
||||||
|
|
||||||
|
func newAlternateTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priDefault,
|
||||||
|
evalFunc: evalAlternate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalAlternate(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
var rightValue any
|
||||||
|
|
||||||
|
if err = opTerm.checkOperands(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
leftTerm := opTerm.children[0]
|
||||||
|
if leftTerm.tk.Sym != SymVariable {
|
||||||
|
// err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
||||||
|
err = ErrLeftOperandMustBeVariable(leftTerm, opTerm)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists && leftValue != nil {
|
||||||
|
if rightValue, err = opTerm.children[1].compute(ctx); err == nil {
|
||||||
|
v = rightValue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v = leftValue
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------- default assign term
|
||||||
|
|
||||||
|
func newDefaultAssignTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priDefault,
|
||||||
|
evalFunc: evalAssignDefault,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalAssignDefault(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
var rightValue any
|
||||||
|
|
||||||
|
if err = opTerm.checkOperands(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
leftTerm := opTerm.children[0]
|
||||||
|
if leftTerm.tk.Sym != SymVariable {
|
||||||
|
// err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
|
||||||
|
err = ErrLeftOperandMustBeVariable(leftTerm, opTerm)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
|
||||||
|
v = leftValue
|
||||||
|
} else if rightValue, err = opTerm.children[1].compute(ctx); err == nil {
|
||||||
|
if functor, ok := rightValue.(Functor); ok {
|
||||||
|
//ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
|
||||||
|
ctx.RegisterFunc(leftTerm.source(), functor, TypeAny, []ExprFuncParam{
|
||||||
|
NewFuncParamFlag(ParamValue, PfDefault|PfRepeat),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
v = rightValue
|
||||||
|
ctx.UnsafeSetVar(leftTerm.source(), rightValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymDoubleQuestion, newDefaultTerm)
|
||||||
|
registerTermConstructor(SymQuestionEqual, newDefaultAssignTerm)
|
||||||
|
registerTermConstructor(SymQuestionExclam, newAlternateTerm)
|
||||||
|
}
|
||||||
+10
-56
@@ -4,8 +4,6 @@
|
|||||||
// operator-dot.go
|
// operator-dot.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// -------- dot term
|
// -------- dot term
|
||||||
func newDotTerm(tk *Token) (inst *term) {
|
func newDotTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
@@ -17,69 +15,21 @@ func newDotTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyIndex(ctx ExprContext, indexTerm *term, maxValue int) (index int, err error) {
|
func evalDot(ctx ExprContext, opTerm *term) (v any, 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 {
|
|
||||||
index = v
|
|
||||||
} else if index >= -maxValue {
|
|
||||||
index = maxValue + 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
|
var leftValue, rightValue any
|
||||||
|
|
||||||
if err = self.checkOperands(); err != nil {
|
if err = opTerm.checkOperands(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if leftValue, err = self.children[0].compute(ctx); err != nil {
|
if leftValue, err = opTerm.children[0].compute(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
indexTerm := self.children[1]
|
indexTerm := opTerm.children[1]
|
||||||
|
|
||||||
switch unboxedValue := leftValue.(type) {
|
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 = unboxedValue[index]
|
|
||||||
}
|
|
||||||
case map[any]any:
|
|
||||||
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 *dataCursor:
|
|
||||||
// if indexTerm.symbol() == SymIdentifier {
|
|
||||||
// opName := indexTerm.source()
|
|
||||||
// if opName == resetName {
|
|
||||||
// _, err = unboxedValue.Reset()
|
|
||||||
// } else if opName == cleanName {
|
|
||||||
// _, err = unboxedValue.Clean()
|
|
||||||
// } else {
|
|
||||||
// err = indexTerm.Errorf("iterators do not support command %q", opName)
|
|
||||||
// }
|
|
||||||
// v = err == nil
|
|
||||||
// }
|
|
||||||
case ExtIterator:
|
case ExtIterator:
|
||||||
if indexTerm.symbol() == SymIdentifier {
|
if indexTerm.symbol() == SymVariable /*|| indexTerm.symbol() == SymString */ {
|
||||||
opName := indexTerm.source()
|
opName := indexTerm.source()
|
||||||
if unboxedValue.HasOperation(opName) {
|
if unboxedValue.HasOperation(opName) {
|
||||||
v, err = unboxedValue.CallOperation(opName, []any{})
|
v, err = unboxedValue.CallOperation(opName, []any{})
|
||||||
@@ -87,9 +37,13 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
err = indexTerm.Errorf("this iterator do not support the %q command", opName)
|
err = indexTerm.Errorf("this iterator do not support the %q command", opName)
|
||||||
v = false
|
v = false
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
err = indexTerm.tk.ErrorExpectedGot("identifier")
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
if rightValue, err = opTerm.children[1].compute(ctx); err == nil {
|
||||||
|
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -18,14 +18,14 @@ func newFactTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalFact(ctx ExprContext, self *term) (v any, err error) {
|
func evalFact(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
var leftValue any
|
var leftValue any
|
||||||
|
|
||||||
if leftValue, err = self.evalPrefix(ctx); err != nil {
|
if leftValue, err = opTerm.evalPrefix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isInteger(leftValue) {
|
if IsInteger(leftValue) {
|
||||||
if i, _ := leftValue.(int64); i >= 0 {
|
if i, _ := leftValue.(int64); i >= 0 {
|
||||||
f := int64(1)
|
f := int64(1)
|
||||||
for k := int64(1); k <= i; k++ {
|
for k := int64(1); k <= i; k++ {
|
||||||
@@ -36,7 +36,7 @@ func evalFact(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
err = fmt.Errorf("factorial of a negative integer (%d) is not allowed", i)
|
err = fmt.Errorf("factorial of a negative integer (%d) is not allowed", i)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleType(leftValue)
|
err = opTerm.errIncompatibleType(leftValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-221
@@ -4,72 +4,13 @@
|
|||||||
// operand-fraction.go
|
// operand-fraction.go
|
||||||
package expr
|
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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"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 (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()
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- fraction term
|
// -------- fraction term
|
||||||
func newFractionTerm(tk *Token) *term {
|
func newFractionTerm(tk *Token) *term {
|
||||||
return &term{
|
return &term{
|
||||||
@@ -83,12 +24,12 @@ func newFractionTerm(tk *Token) *term {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------- eval func
|
// -------- eval func
|
||||||
func evalFraction(ctx ExprContext, self *term) (v any, err error) {
|
func evalFraction(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
var numValue, denValue any
|
var numValue, denValue any
|
||||||
var num, den int64
|
var num, den int64
|
||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
if numValue, denValue, err = self.evalInfix(ctx); err != nil {
|
if numValue, denValue, err = opTerm.evalInfix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if num, ok = numValue.(int64); !ok {
|
if num, ok = numValue.(int64); !ok {
|
||||||
@@ -114,168 +55,11 @@ func evalFraction(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
if den == 1 {
|
if den == 1 {
|
||||||
v = num
|
v = num
|
||||||
} else {
|
} else {
|
||||||
v = &fraction{num, den}
|
v = &FractionType{num, den}
|
||||||
}
|
}
|
||||||
return
|
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
|
// init
|
||||||
func init() {
|
func init() {
|
||||||
registerTermConstructor(SymVertBar, newFractionTerm)
|
registerTermConstructor(SymVertBar, newFractionTerm)
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-in.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
//-------- in term
|
||||||
|
|
||||||
|
func newInTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priRelational,
|
||||||
|
evalFunc: evalIn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// func hasKey(d map[any]any, target any) (ok bool) {
|
||||||
|
// _, ok = d[target]
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func evalIn(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsList(rightValue) {
|
||||||
|
list, _ := rightValue.(*ListType)
|
||||||
|
v = list.indexDeepSameCmp(leftValue) >= 0
|
||||||
|
} else if IsDict(rightValue) {
|
||||||
|
dict, _ := rightValue.(*DictType)
|
||||||
|
v = dict.hasKey(leftValue)
|
||||||
|
} else {
|
||||||
|
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymKwIn, newInTerm)
|
||||||
|
}
|
||||||
+16
-13
@@ -16,37 +16,40 @@ func newIncludeTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalInclude(ctx ExprContext, self *term) (v any, err error) {
|
func evalInclude(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
var childValue any
|
var childValue any
|
||||||
|
|
||||||
if childValue, err = self.evalPrefix(ctx); err != nil {
|
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
count := 0
|
count := 0
|
||||||
if isList(childValue) {
|
if IsList(childValue) {
|
||||||
list, _ := childValue.([]any)
|
list, _ := childValue.(*ListType)
|
||||||
for i, filePathSpec := range list {
|
for i, filePathSpec := range *list {
|
||||||
if filePath, ok := filePathSpec.(string); ok {
|
if filePath, ok := filePathSpec.(string); ok {
|
||||||
if v, err = EvalFile(ctx, filePath); err == nil {
|
if v, err = EvalFile(ctx, filePath); err == nil {
|
||||||
count++
|
count++
|
||||||
} else {
|
} else {
|
||||||
err = self.Errorf("can't load file %q", filePath)
|
err = opTerm.Errorf("can't load file %q", filePath)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.Errorf("expected string at item nr %d, got %T", i+1, filePathSpec)
|
err = opTerm.Errorf("expected string at item nr %d, got %T", i+1, filePathSpec)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if isString(childValue) {
|
} else if IsString(childValue) {
|
||||||
filePath, _ := childValue.(string)
|
filePath, _ := childValue.(string)
|
||||||
v, err = EvalFile(ctx, filePath)
|
if v, err = EvalFile(ctx, filePath); err == nil {
|
||||||
} else {
|
count++
|
||||||
err = self.errIncompatibleType(childValue)
|
|
||||||
}
|
}
|
||||||
if err == nil {
|
} else {
|
||||||
v = count
|
err = opTerm.errIncompatibleType(childValue)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
//v = count
|
||||||
|
v = nil
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
// 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(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 = ToGoInt((*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 endIndex == ConstLastIndex {
|
||||||
|
endIndex = maxValue
|
||||||
|
}
|
||||||
|
if startIndex < 0 && startIndex >= -maxValue {
|
||||||
|
startIndex = maxValue + startIndex
|
||||||
|
}
|
||||||
|
if endIndex < 0 && endIndex >= -maxValue {
|
||||||
|
endIndex = maxValue + endIndex
|
||||||
|
}
|
||||||
|
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, opTerm *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
var indexList *ListType
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
indexTerm := opTerm.children[1]
|
||||||
|
if indexList, ok = rightValue.(*ListType); !ok {
|
||||||
|
err = opTerm.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:
|
||||||
|
v, err = getDictItem(unboxedValue, indexTerm, indexList, rightValue)
|
||||||
|
default:
|
||||||
|
err = opTerm.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 = opTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
} else if IsDict(leftValue) {
|
||||||
|
d := leftValue.(*DictType)
|
||||||
|
v, err = getDictItem(d, indexTerm, indexList, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDictItem(d *DictType, indexTerm *term, indexList *ListType, rightValue any) (v any, err error) {
|
||||||
|
var ok bool
|
||||||
|
var indexValue any
|
||||||
|
|
||||||
|
if indexValue, err = verifyKey(indexList); err == nil {
|
||||||
|
if v, ok = (*d)[indexValue]; !ok {
|
||||||
|
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymIndex, newIndexTerm)
|
||||||
|
}
|
||||||
+32
-8
@@ -26,40 +26,64 @@ func newAppendTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalInsert(ctx ExprContext, self *term) (v any, err error) {
|
func evalInsert(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
var leftValue, rightValue any
|
||||||
|
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isList(rightValue) {
|
if IsList(rightValue) {
|
||||||
list, _ := rightValue.(*ListType)
|
list, _ := rightValue.(*ListType)
|
||||||
newList := append(ListType{leftValue}, *list...)
|
newList := append(ListType{leftValue}, *list...)
|
||||||
v = &newList
|
v = &newList
|
||||||
|
if opTerm.children[1].symbol() == SymVariable {
|
||||||
|
ctx.UnsafeSetVar(opTerm.children[1].source(), v)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalAppend(ctx ExprContext, self *term) (v any, err error) {
|
func evalAppend(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
var leftValue, rightValue any
|
||||||
|
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isList(leftValue) {
|
if IsList(leftValue) {
|
||||||
list, _ := leftValue.(*ListType)
|
list, _ := leftValue.(*ListType)
|
||||||
newList := append(*list, rightValue)
|
newList := append(*list, rightValue)
|
||||||
v = &newList
|
v = &newList
|
||||||
|
if opTerm.children[0].symbol() == SymVariable {
|
||||||
|
ctx.UnsafeSetVar(opTerm.children[0].source(), v)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func evalAssignAppend(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
// var leftValue, rightValue any
|
||||||
|
|
||||||
|
// if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if IsList(leftValue) {
|
||||||
|
// list, _ := leftValue.(*ListType)
|
||||||
|
// newList := append(*list, rightValue)
|
||||||
|
// v = &newList
|
||||||
|
// if
|
||||||
|
// } else {
|
||||||
|
// err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
// init
|
// init
|
||||||
func init() {
|
func init() {
|
||||||
registerTermConstructor(SymInsert, newInsertTerm)
|
registerTermConstructor(SymInsert, newInsertTerm)
|
||||||
|
|||||||
@@ -16,22 +16,23 @@ func newIterValueTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalIterValue(ctx ExprContext, self *term) (v any, err error) {
|
func evalIterValue(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
var childValue any
|
var childValue any
|
||||||
|
|
||||||
if childValue, err = self.evalPrefix(ctx); err != nil {
|
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if it, ok := childValue.(Iterator); ok {
|
if it, ok := childValue.(Iterator); ok {
|
||||||
v, err = it.Current()
|
v, err = it.Current()
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleType(childValue)
|
err = opTerm.errIncompatibleType(childValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// init
|
// init
|
||||||
func init() {
|
func init() {
|
||||||
registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
|
// registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
|
||||||
|
registerTermConstructor(SymCaret, newIterValueTerm)
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-8
@@ -16,28 +16,32 @@ func newLengthTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalLength(ctx ExprContext, self *term) (v any, err error) {
|
func evalLength(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
var childValue any
|
var childValue any
|
||||||
|
|
||||||
if childValue, err = self.evalPrefix(ctx); err != nil {
|
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isList(childValue) {
|
if IsList(childValue) {
|
||||||
list, _ := childValue.([]any)
|
ls, _ := childValue.(*ListType)
|
||||||
v = int64(len(list))
|
v = int64(len(*ls))
|
||||||
} else if isString(childValue) {
|
} else if IsString(childValue) {
|
||||||
s, _ := childValue.(string)
|
s, _ := childValue.(string)
|
||||||
v = int64(len(s))
|
v = int64(len(s))
|
||||||
|
} else if IsDict(childValue) {
|
||||||
|
// m, _ := childValue.(map[any]any)
|
||||||
|
m, _ := childValue.(*DictType)
|
||||||
|
v = int64(len(*m))
|
||||||
} else if it, ok := childValue.(Iterator); ok {
|
} else if it, ok := childValue.(Iterator); ok {
|
||||||
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) {
|
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) {
|
||||||
count, _ := extIt.CallOperation(countName, nil)
|
count, _ := extIt.CallOperation(countName, nil)
|
||||||
v, _ = toInt(count, "")
|
v, _ = ToGoInt(count, "")
|
||||||
} else {
|
} else {
|
||||||
v = int64(it.Index() + 1)
|
v = int64(it.Index() + 1)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleType(childValue)
|
err = opTerm.errIncompatibleType(childValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, opTerm *term) (v any, err error) {
|
||||||
|
var childValue any
|
||||||
|
var moduleSpec any
|
||||||
|
|
||||||
|
if childValue, err = opTerm.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 = opTerm.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)
|
||||||
|
}
|
||||||
@@ -17,20 +17,20 @@ func newPostIncTerm(tk *Token) *term {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
|
func evalPostInc(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
var childValue any
|
var childValue any
|
||||||
if childValue, err = self.evalPrefix(ctx); err != nil {
|
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if it, ok := childValue.(Iterator); ok {
|
if it, ok := childValue.(Iterator); ok {
|
||||||
v, err = it.Next()
|
v, err = it.Next()
|
||||||
} else if isInteger(childValue) && self.children[0].symbol() == SymIdentifier {
|
} else if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
|
||||||
v = childValue
|
v = childValue
|
||||||
i, _ := childValue.(int64)
|
i, _ := childValue.(int64)
|
||||||
ctx.SetVar(self.children[0].source(), i+1)
|
ctx.SetVar(opTerm.children[0].source(), i+1)
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleType(childValue)
|
err = opTerm.errIncompatibleType(childValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-31
@@ -14,8 +14,6 @@ import (
|
|||||||
func newMultiplyTerm(tk *Token) (inst *term) {
|
func newMultiplyTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindUnknown,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priProduct,
|
priority: priProduct,
|
||||||
@@ -23,29 +21,29 @@ func newMultiplyTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalMultiply(ctx ExprContext, self *term) (v any, err error) {
|
func evalMultiply(ctx ExprContext, prodTerm *term) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
var leftValue, rightValue any
|
||||||
|
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
if leftValue, rightValue, err = prodTerm.evalInfix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isString(leftValue) && isInteger(rightValue) {
|
if IsString(leftValue) && IsInteger(rightValue) {
|
||||||
s, _ := leftValue.(string)
|
s, _ := leftValue.(string)
|
||||||
n, _ := rightValue.(int64)
|
n, _ := rightValue.(int64)
|
||||||
v = strings.Repeat(s, int(n))
|
v = strings.Repeat(s, int(n))
|
||||||
} else if isNumber(leftValue) && isNumber(rightValue) {
|
} else if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
|
||||||
if isFloat(leftValue) || isFloat(rightValue) {
|
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||||
v = numAsFloat(leftValue) * numAsFloat(rightValue)
|
v = numAsFloat(leftValue) * numAsFloat(rightValue)
|
||||||
|
} else if isFraction(leftValue) || isFraction(rightValue) {
|
||||||
|
v, err = mulAnyFract(leftValue, rightValue)
|
||||||
} else {
|
} else {
|
||||||
leftInt, _ := leftValue.(int64)
|
leftInt, _ := leftValue.(int64)
|
||||||
rightInt, _ := rightValue.(int64)
|
rightInt, _ := rightValue.(int64)
|
||||||
v = leftInt * rightInt
|
v = leftInt * rightInt
|
||||||
}
|
}
|
||||||
} else if isFraction(leftValue) || isFraction(rightValue) {
|
|
||||||
v, err = mulAnyFract(leftValue, rightValue)
|
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = prodTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -55,8 +53,6 @@ func evalMultiply(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
func newDivideTerm(tk *Token) (inst *term) {
|
func newDivideTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindUnknown,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priProduct,
|
priority: priProduct,
|
||||||
@@ -64,21 +60,23 @@ func newDivideTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalDivide(ctx ExprContext, self *term) (v any, err error) {
|
func evalDivide(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
var leftValue, rightValue any
|
||||||
|
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNumber(leftValue) && isNumber(rightValue) {
|
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
|
||||||
if isFloat(leftValue) || isFloat(rightValue) {
|
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||||
d := numAsFloat(rightValue)
|
d := numAsFloat(rightValue)
|
||||||
if d == 0.0 {
|
if d == 0.0 {
|
||||||
err = errors.New("division by zero")
|
err = errors.New("division by zero")
|
||||||
} else {
|
} else {
|
||||||
v = numAsFloat(leftValue) / d
|
v = numAsFloat(leftValue) / d
|
||||||
}
|
}
|
||||||
|
} else if isFraction(leftValue) || isFraction(rightValue) {
|
||||||
|
v, err = divAnyFract(leftValue, rightValue)
|
||||||
} else {
|
} else {
|
||||||
leftInt, _ := leftValue.(int64)
|
leftInt, _ := leftValue.(int64)
|
||||||
if rightInt, _ := rightValue.(int64); rightInt == 0 {
|
if rightInt, _ := rightValue.(int64); rightInt == 0 {
|
||||||
@@ -87,10 +85,8 @@ func evalDivide(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
v = leftInt / rightInt
|
v = leftInt / rightInt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if isFraction(leftValue) || isFraction(rightValue) {
|
|
||||||
v, err = divAnyFract(leftValue, rightValue)
|
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -107,14 +103,14 @@ func newDivideAsFloatTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalDivideAsFloat(ctx ExprContext, self *term) (v any, err error) {
|
func evalDivideAsFloat(ctx ExprContext, floatDivTerm *term) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
var leftValue, rightValue any
|
||||||
|
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
if leftValue, rightValue, err = floatDivTerm.evalInfix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNumber(leftValue) && isNumber(rightValue) {
|
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
|
||||||
d := numAsFloat(rightValue)
|
d := numAsFloat(rightValue)
|
||||||
if d == 0.0 {
|
if d == 0.0 {
|
||||||
err = errors.New("division by zero")
|
err = errors.New("division by zero")
|
||||||
@@ -122,18 +118,16 @@ func evalDivideAsFloat(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
v = numAsFloat(leftValue) / d
|
v = numAsFloat(leftValue) / d
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = floatDivTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------- reminder term
|
//-------- reminder term
|
||||||
|
|
||||||
func newReminderTerm(tk *Token) (inst *term) {
|
func newRemainderTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindUnknown,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priProduct,
|
priority: priProduct,
|
||||||
@@ -141,14 +135,14 @@ func newReminderTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalReminder(ctx ExprContext, self *term) (v any, err error) {
|
func evalReminder(ctx ExprContext, ramainderTerm *term) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
var leftValue, rightValue any
|
||||||
|
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
if leftValue, rightValue, err = ramainderTerm.evalInfix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isInteger(leftValue) && isInteger(rightValue) {
|
if IsInteger(leftValue) && IsInteger(rightValue) {
|
||||||
rightInt, _ := rightValue.(int64)
|
rightInt, _ := rightValue.(int64)
|
||||||
if rightInt == 0 {
|
if rightInt == 0 {
|
||||||
err = errors.New("division by zero")
|
err = errors.New("division by zero")
|
||||||
@@ -157,7 +151,7 @@ func evalReminder(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
v = leftInt % rightInt
|
v = leftInt % rightInt
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = ramainderTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -167,5 +161,5 @@ func init() {
|
|||||||
registerTermConstructor(SymStar, newMultiplyTerm)
|
registerTermConstructor(SymStar, newMultiplyTerm)
|
||||||
registerTermConstructor(SymSlash, newDivideTerm)
|
registerTermConstructor(SymSlash, newDivideTerm)
|
||||||
registerTermConstructor(SymDotSlash, newDivideAsFloatTerm)
|
registerTermConstructor(SymDotSlash, newDivideAsFloatTerm)
|
||||||
registerTermConstructor(SymPercent, newReminderTerm)
|
registerTermConstructor(SymPercent, newRemainderTerm)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: priRange,
|
||||||
|
evalFunc: evalRange,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalRange(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
// if err = self.checkOperands(); err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
if len(opTerm.children) == 0 {
|
||||||
|
leftValue = int64(0)
|
||||||
|
rightValue = int64(-1)
|
||||||
|
} else if len(opTerm.children) == 1 {
|
||||||
|
if leftValue, err = opTerm.children[0].compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rightValue = int64(ConstLastIndex)
|
||||||
|
} else if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !(IsInteger(leftValue) && IsInteger(rightValue)) {
|
||||||
|
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
startIndex, _ := leftValue.(int64)
|
||||||
|
endIndex, _ := rightValue.(int64)
|
||||||
|
|
||||||
|
v = &intPair{int(startIndex), int(endIndex)}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymColon, newRangeTerm)
|
||||||
|
}
|
||||||
+101
-99
@@ -4,13 +4,13 @@
|
|||||||
// operator-rel.go
|
// operator-rel.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
//-------- equal term
|
//-------- equal term
|
||||||
|
|
||||||
func newEqualTerm(tk *Token) (inst *term) {
|
func newEqualTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindBool,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priRelational,
|
priority: priRelational,
|
||||||
@@ -18,28 +18,41 @@ func newEqualTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalEqual(ctx ExprContext, self *term) (v any, err error) {
|
type deepFuncTemplate func(a, b any) (eq bool, err error)
|
||||||
var leftValue, rightValue any
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNumber(leftValue) && isNumber(rightValue) {
|
func evalEqual(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
if isInteger(leftValue) && isInteger(rightValue) {
|
var leftValue, rightValue any
|
||||||
li, _ := leftValue.(int64)
|
|
||||||
ri, _ := rightValue.(int64)
|
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
|
||||||
v = li == ri
|
return
|
||||||
} 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,8 +61,6 @@ func evalEqual(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
func newNotEqualTerm(tk *Token) (inst *term) {
|
func newNotEqualTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindBool,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priRelational,
|
priority: priRelational,
|
||||||
@@ -57,46 +68,19 @@ func newNotEqualTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalNotEqual(ctx ExprContext, self *term) (v any, err error) {
|
func evalNotEqual(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
if v, err = evalEqual(ctx, self); err == nil {
|
if v, err = evalEqual(ctx, opTerm); err == nil {
|
||||||
b, _ := toBool(v)
|
b, _ := ToBool(v)
|
||||||
v = !b
|
v = !b
|
||||||
}
|
}
|
||||||
return
|
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
|
//-------- less term
|
||||||
|
|
||||||
func newLessTerm(tk *Token) (inst *term) {
|
func newLessTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindBool,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priRelational,
|
priority: priRelational,
|
||||||
@@ -104,28 +88,44 @@ func newLessTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalLess(ctx ExprContext, self *term) (v any, err error) {
|
func lessThan(self *term, a, b any) (isLess bool, err error) {
|
||||||
var leftValue, rightValue any
|
if isNumOrFract(a) && isNumOrFract(b) {
|
||||||
|
if IsNumber(a) && IsNumber(b) {
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNumber(leftValue) && isNumber(rightValue) {
|
func evalLess(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
if isInteger(leftValue) && isInteger(rightValue) {
|
var leftValue, rightValue any
|
||||||
li, _ := leftValue.(int64)
|
|
||||||
ri, _ := rightValue.(int64)
|
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
|
||||||
v = li < ri
|
return
|
||||||
} 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(opTerm, leftValue, rightValue)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,28 +141,28 @@ func newLessEqualTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
|
func lessThanOrEqual(self *term, a, b any) (isLessEq bool, err error) {
|
||||||
var leftValue, rightValue any
|
if isLessEq, err = lessThan(self, a, b); err == nil {
|
||||||
|
if !isLessEq {
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
if IsList(a) && IsList(b) {
|
||||||
|
isLessEq, err = sameContent(a, b)
|
||||||
|
} else {
|
||||||
|
isLessEq, err = equals(a, b, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNumber(leftValue) && isNumber(rightValue) {
|
func evalLessEqual(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
if isInteger(leftValue) && isInteger(rightValue) {
|
var leftValue, rightValue any
|
||||||
li, _ := leftValue.(int64)
|
|
||||||
ri, _ := rightValue.(int64)
|
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
|
||||||
v = li <= ri
|
return
|
||||||
} 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(opTerm, leftValue, rightValue)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,8 +171,6 @@ func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
func newGreaterTerm(tk *Token) (inst *term) {
|
func newGreaterTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindBool,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priRelational,
|
priority: priRelational,
|
||||||
@@ -180,11 +178,14 @@ func newGreaterTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalGreater(ctx ExprContext, self *term) (v any, err error) {
|
func evalGreater(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
if v, err = evalLessEqual(ctx, self); err == nil {
|
var leftValue, rightValue any
|
||||||
b, _ := toBool(v)
|
|
||||||
v = !b
|
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v, err = lessThan(opTerm, rightValue, leftValue)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,8 +194,6 @@ func evalGreater(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
func newGreaterEqualTerm(tk *Token) (inst *term) {
|
func newGreaterEqualTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
// class: classOperator,
|
|
||||||
// kind: kindBool,
|
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priRelational,
|
priority: priRelational,
|
||||||
@@ -202,11 +201,14 @@ func newGreaterEqualTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalGreaterEqual(ctx ExprContext, self *term) (v any, err error) {
|
func evalGreaterEqual(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
if v, err = evalLess(ctx, self); err == nil {
|
var leftValue, rightValue any
|
||||||
b, _ := toBool(v)
|
|
||||||
v = !b
|
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v, err = lessThanOrEqual(opTerm, rightValue, leftValue)
|
||||||
return
|
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) {
|
func trySelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (match bool, selectedValue any, err error) {
|
||||||
caseData, _ := caseSel.(*selectorCase)
|
caseData, _ := caseSel.(*selectorCase)
|
||||||
if caseData.filterList == nil {
|
if caseData.filterList == nil {
|
||||||
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
selectedValue, err = caseData.caseExpr.Eval(ctx)
|
||||||
match = true
|
match = true
|
||||||
} else if filterList, ok := caseData.filterList.value().([]*term); ok {
|
} else if filterList, ok := caseData.filterList.value().([]*term); ok {
|
||||||
if len(filterList) == 0 && exprValue == int64(caseIndex) {
|
if len(filterList) == 0 && exprValue == int64(caseIndex) {
|
||||||
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
selectedValue, err = caseData.caseExpr.Eval(ctx)
|
||||||
match = true
|
match = true
|
||||||
} else {
|
} else {
|
||||||
var caseValue any
|
var caseValue any
|
||||||
for _, caseTerm := range filterList {
|
for _, caseTerm := range filterList {
|
||||||
if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue {
|
if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue {
|
||||||
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
selectedValue, err = caseData.caseExpr.Eval(ctx)
|
||||||
match = true
|
match = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -39,19 +39,19 @@ func trySelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (ma
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalSelector(ctx ExprContext, self *term) (v any, err error) {
|
func evalSelector(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
var exprValue any
|
var exprValue any
|
||||||
var match bool
|
var match bool
|
||||||
|
|
||||||
if err = self.checkOperands(); err != nil {
|
if err = opTerm.checkOperands(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
exprTerm := self.children[0]
|
exprTerm := opTerm.children[0]
|
||||||
if exprValue, err = exprTerm.compute(ctx); err != nil {
|
if exprValue, err = exprTerm.compute(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
caseListTerm := self.children[1]
|
caseListTerm := opTerm.children[1]
|
||||||
caseList, _ := caseListTerm.value().([]*term)
|
caseList, _ := caseListTerm.value().([]*term)
|
||||||
for i, caseTerm := range caseList {
|
for i, caseTerm := range caseList {
|
||||||
caseSel := caseTerm.value()
|
caseSel := caseTerm.value()
|
||||||
|
|||||||
+7
-7
@@ -28,29 +28,29 @@ func newMinusSignTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalSign(ctx ExprContext, self *term) (v any, err error) {
|
func evalSign(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
var rightValue any
|
var rightValue any
|
||||||
|
|
||||||
if rightValue, err = self.evalPrefix(ctx); err != nil {
|
if rightValue, err = opTerm.evalPrefix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isFloat(rightValue) {
|
if IsFloat(rightValue) {
|
||||||
if self.tk.Sym == SymChangeSign {
|
if opTerm.tk.Sym == SymChangeSign {
|
||||||
f, _ := rightValue.(float64)
|
f, _ := rightValue.(float64)
|
||||||
v = -f
|
v = -f
|
||||||
} else {
|
} else {
|
||||||
v = rightValue
|
v = rightValue
|
||||||
}
|
}
|
||||||
} else if isInteger(rightValue) {
|
} else if IsInteger(rightValue) {
|
||||||
if self.tk.Sym == SymChangeSign {
|
if opTerm.tk.Sym == SymChangeSign {
|
||||||
i, _ := rightValue.(int64)
|
i, _ := rightValue.(int64)
|
||||||
v = -i
|
v = -i
|
||||||
} else {
|
} else {
|
||||||
v = rightValue
|
v = rightValue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleType(rightValue)
|
err = opTerm.errIncompatibleType(rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-30
@@ -21,44 +21,46 @@ func newPlusTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalPlus(ctx ExprContext, self *term) (v any, err error) {
|
func evalPlus(ctx ExprContext, plusTerm *term) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
var leftValue, rightValue any
|
||||||
|
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
if leftValue, rightValue, err = plusTerm.evalInfix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isString(leftValue) && isNumberString(rightValue)) || (isString(rightValue) && isNumberString(leftValue)) {
|
if (IsString(leftValue) && isNumberString(rightValue)) || (IsString(rightValue) && isNumberString(leftValue)) {
|
||||||
v = fmt.Sprintf("%v%v", leftValue, rightValue)
|
v = fmt.Sprintf("%v%v", leftValue, rightValue)
|
||||||
} else if isNumber(leftValue) && isNumber(rightValue) {
|
} else if IsNumber(leftValue) && IsNumber(rightValue) {
|
||||||
if isFloat(leftValue) || isFloat(rightValue) {
|
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||||
v = numAsFloat(leftValue) + numAsFloat(rightValue)
|
v = numAsFloat(leftValue) + numAsFloat(rightValue)
|
||||||
} else {
|
} else {
|
||||||
leftInt, _ := leftValue.(int64)
|
leftInt, _ := leftValue.(int64)
|
||||||
rightInt, _ := rightValue.(int64)
|
rightInt, _ := rightValue.(int64)
|
||||||
v = leftInt + rightInt
|
v = leftInt + rightInt
|
||||||
}
|
}
|
||||||
} else if isList(leftValue) || isList(rightValue) {
|
} else if IsList(leftValue) && IsList(rightValue) {
|
||||||
var leftList, rightList *ListType
|
var leftList, rightList *ListType
|
||||||
var ok bool
|
leftList, _ = leftValue.(*ListType)
|
||||||
if leftList, ok = leftValue.(*ListType); !ok {
|
rightList, _ = rightValue.(*ListType)
|
||||||
leftList = &ListType{leftValue}
|
|
||||||
}
|
|
||||||
if rightList, ok = rightValue.(*ListType); !ok {
|
|
||||||
rightList = &ListType{rightValue}
|
|
||||||
}
|
|
||||||
sumList := make(ListType, 0, len(*leftList)+len(*rightList))
|
sumList := make(ListType, 0, len(*leftList)+len(*rightList))
|
||||||
for _, item := range *leftList {
|
sumList = append(sumList, *leftList...)
|
||||||
sumList = append(sumList, item)
|
sumList = append(sumList, *rightList...)
|
||||||
}
|
|
||||||
for _, item := range *rightList {
|
|
||||||
sumList = append(sumList, item)
|
|
||||||
}
|
|
||||||
v = &sumList
|
v = &sumList
|
||||||
} else if isFraction(leftValue) || isFraction(rightValue) {
|
} else if (isFraction(leftValue) && IsNumber(rightValue)) || (isFraction(rightValue) && IsNumber(leftValue)) {
|
||||||
v, err = sumAnyFract(leftValue, rightValue)
|
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||||
|
v = numAsFloat(leftValue) + numAsFloat(rightValue)
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
v, err = sumAnyFract(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
} else if IsDict(leftValue) && IsDict(rightValue) {
|
||||||
|
leftDict, _ := leftValue.(*DictType)
|
||||||
|
rightDict, _ := rightValue.(*DictType)
|
||||||
|
c := leftDict.clone()
|
||||||
|
c.merge(rightDict)
|
||||||
|
v = c
|
||||||
|
} else {
|
||||||
|
err = plusTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -75,22 +77,24 @@ func newMinusTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalMinus(ctx ExprContext, self *term) (v any, err error) {
|
func evalMinus(ctx ExprContext, minusTerm *term) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
var leftValue, rightValue any
|
||||||
|
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
if leftValue, rightValue, err = minusTerm.evalInfix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isNumber(leftValue) && isNumber(rightValue) {
|
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
|
||||||
if isFloat(leftValue) || isFloat(rightValue) {
|
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||||
v = numAsFloat(leftValue) - numAsFloat(rightValue)
|
v = numAsFloat(leftValue) - numAsFloat(rightValue)
|
||||||
|
} else if isFraction(leftValue) || isFraction(rightValue) {
|
||||||
|
v, err = subAnyFract(leftValue, rightValue)
|
||||||
} else {
|
} else {
|
||||||
leftInt, _ := leftValue.(int64)
|
leftInt, _ := leftValue.(int64)
|
||||||
rightInt, _ := rightValue.(int64)
|
rightInt, _ := rightValue.(int64)
|
||||||
v = leftInt - rightInt
|
v = leftInt - rightInt
|
||||||
}
|
}
|
||||||
} else if isList(leftValue) && isList(rightValue) {
|
} else if IsList(leftValue) && IsList(rightValue) {
|
||||||
leftList, _ := leftValue.(*ListType)
|
leftList, _ := leftValue.(*ListType)
|
||||||
rightList, _ := rightValue.(*ListType)
|
rightList, _ := rightValue.(*ListType)
|
||||||
diffList := make(ListType, 0, len(*leftList)-len(*rightList))
|
diffList := make(ListType, 0, len(*leftList)-len(*rightList))
|
||||||
@@ -100,10 +104,8 @@ func evalMinus(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
v = &diffList
|
v = &diffList
|
||||||
} else if isFraction(leftValue) || isFraction(rightValue) {
|
|
||||||
v, err = subAnyFract(leftValue, rightValue)
|
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = minusTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-unset.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
//-------- unset term
|
||||||
|
|
||||||
|
func newUnsetTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 1),
|
||||||
|
position: posPrefix,
|
||||||
|
priority: priSign,
|
||||||
|
evalFunc: evalUnset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteContextItem(ctx ExprContext, opTerm *term, item any) (deleted bool, err error) {
|
||||||
|
if name, ok := item.(string); ok {
|
||||||
|
var size int
|
||||||
|
if strings.HasSuffix(name, "()") {
|
||||||
|
size = ctx.FuncCount()
|
||||||
|
ctx.DeleteFunc(strings.TrimRight(name, "()"))
|
||||||
|
deleted = ctx.FuncCount() < size
|
||||||
|
} else {
|
||||||
|
size = ctx.VarCount()
|
||||||
|
ctx.DeleteVar(name)
|
||||||
|
deleted = ctx.VarCount() < size
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = opTerm.errIncompatibleType(item)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalUnset(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
var childValue any
|
||||||
|
var deleted bool
|
||||||
|
|
||||||
|
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
if IsList(childValue) {
|
||||||
|
list, _ := childValue.(*ListType)
|
||||||
|
for _, item := range *list {
|
||||||
|
if deleted, err = deleteContextItem(ctx, opTerm, item); err != nil {
|
||||||
|
break
|
||||||
|
} else if deleted {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if deleted, err = deleteContextItem(ctx, opTerm, childValue); err == nil && deleted {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
v = int64(count)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymKwUnset, newUnsetTerm)
|
||||||
|
}
|
||||||
@@ -6,51 +6,41 @@ package expr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//-------- parser
|
//-------- parser
|
||||||
|
|
||||||
type parser struct {
|
type parser struct {
|
||||||
ctx ExprContext
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewParser(ctx ExprContext) (p *parser) {
|
func NewParser() (p *parser) {
|
||||||
p = &parser{
|
p = &parser{}
|
||||||
ctx: ctx,
|
|
||||||
}
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token) (tree *term, err error) {
|
func (parser *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token) (tree *term, err error) {
|
||||||
// name, _ := tk.Value.(string)
|
|
||||||
// funcObj := self.ctx.GetFuncInfo(name)
|
|
||||||
// if funcObj == nil {
|
|
||||||
// err = fmt.Errorf("unknown function %s()", name)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// maxArgs := funcObj.MaxArgs()
|
|
||||||
// if maxArgs < 0 {
|
|
||||||
// maxArgs = funcObj.MinArgs() + 10
|
|
||||||
// }
|
|
||||||
// args := make([]*term, 0, maxArgs)
|
|
||||||
args := make([]*term, 0, 10)
|
args := make([]*term, 0, 10)
|
||||||
|
itemExpected := false
|
||||||
lastSym := SymUnknown
|
lastSym := SymUnknown
|
||||||
for lastSym != SymClosedRound && lastSym != SymEos {
|
for lastSym != SymClosedRound && lastSym != SymEos {
|
||||||
var subTree *ast
|
var subTree *ast
|
||||||
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil {
|
if subTree, err = parser.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err != nil {
|
||||||
if subTree.root != nil {
|
|
||||||
args = append(args, subTree.root)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
prev := scanner.Previous()
|
||||||
|
if subTree.root != nil {
|
||||||
|
args = append(args, subTree.root)
|
||||||
|
} else if itemExpected {
|
||||||
|
err = prev.ErrorExpectedGot("function-param-value")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
itemExpected = prev.Sym == SymComma
|
||||||
lastSym = scanner.Previous().Sym
|
lastSym = scanner.Previous().Sym
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// TODO Check arguments
|
|
||||||
if lastSym != SymClosedRound {
|
if lastSym != SymClosedRound {
|
||||||
err = errors.New("unterminate arguments list")
|
err = errors.New("unterminated arguments list")
|
||||||
} else {
|
} else {
|
||||||
tree = newFuncCallTerm(tk, args)
|
tree = newFuncCallTerm(tk, args)
|
||||||
}
|
}
|
||||||
@@ -58,73 +48,34 @@ func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
func (parser *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}
|
// Example: "add = func(x,y) {x+y}
|
||||||
var body *ast
|
var body *ast
|
||||||
args := make([]*term, 0)
|
args := make([]*term, 0)
|
||||||
lastSym := SymUnknown
|
lastSym := SymUnknown
|
||||||
|
defaultParamsStarted := false
|
||||||
itemExpected := false
|
itemExpected := false
|
||||||
tk := scanner.Previous()
|
tk := scanner.Previous()
|
||||||
for lastSym != SymClosedRound && lastSym != SymEos {
|
for lastSym != SymClosedRound && lastSym != SymEos {
|
||||||
tk = scanner.Next()
|
tk = scanner.Next()
|
||||||
if tk.IsSymbol(SymIdentifier) {
|
if tk.IsSymbol(SymIdentifier) {
|
||||||
param := newTerm(tk, nil)
|
param := newTerm(tk)
|
||||||
args = append(args, param)
|
args = append(args, param)
|
||||||
tk = scanner.Next()
|
tk = scanner.Next()
|
||||||
|
if tk.Sym == SymEqual {
|
||||||
|
var paramExpr *ast
|
||||||
|
defaultParamsStarted = true
|
||||||
|
if paramExpr, err = parser.parseItem(scanner, false, SymComma, SymClosedRound); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
param.forceChild(paramExpr.root)
|
||||||
|
} else if defaultParamsStarted {
|
||||||
|
err = tk.Errorf("can't mix default and non-default parameters")
|
||||||
|
break
|
||||||
|
}
|
||||||
} else if itemExpected {
|
} else if itemExpected {
|
||||||
prev := scanner.Previous()
|
prev := scanner.Previous()
|
||||||
err = prev.ErrorExpectedGot("function-param")
|
err = prev.ErrorExpectedGot("function-param-spec")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
lastSym = scanner.Previous().Sym
|
lastSym = scanner.Previous().Sym
|
||||||
@@ -137,13 +88,12 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
tk = scanner.Next()
|
tk = scanner.Next()
|
||||||
if tk.IsSymbol(SymOpenBrace) {
|
if tk.IsSymbol(SymOpenBrace) {
|
||||||
body, err = self.parseGeneral(scanner, true, true, SymClosedBrace)
|
body, err = parser.parseGeneral(scanner, true, true, SymClosedBrace)
|
||||||
} else {
|
} else {
|
||||||
err = tk.ErrorExpectedGot("{")
|
err = tk.ErrorExpectedGot("{")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// TODO Check arguments
|
|
||||||
if scanner.Previous().Sym != SymClosedBrace {
|
if scanner.Previous().Sym != SymClosedBrace {
|
||||||
err = scanner.Previous().ErrorExpectedGot("}")
|
err = scanner.Previous().ErrorExpectedGot("}")
|
||||||
} else {
|
} else {
|
||||||
@@ -154,15 +104,34 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
func (parser *parser) parseList(scanner *scanner, parsingIndeces bool, allowVarRef bool) (subtree *term, err error) {
|
||||||
|
r, c := scanner.lastPos()
|
||||||
args := make([]*term, 0)
|
args := make([]*term, 0)
|
||||||
lastSym := SymUnknown
|
lastSym := SymUnknown
|
||||||
itemExpected := false
|
itemExpected := false
|
||||||
for lastSym != SymClosedSquare && lastSym != SymEos {
|
for lastSym != SymClosedSquare && lastSym != SymEos {
|
||||||
var subTree *ast
|
var subTree *ast
|
||||||
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
|
zeroRequired := scanner.current.Sym == SymColon
|
||||||
if subTree.root != nil {
|
if subTree, err = parser.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == 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 {
|
} else if itemExpected {
|
||||||
prev := scanner.Previous()
|
prev := scanner.Previous()
|
||||||
err = prev.ErrorExpectedGot("list-item")
|
err = prev.ErrorExpectedGot("list-item")
|
||||||
@@ -175,24 +144,23 @@ func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term
|
|||||||
itemExpected = lastSym == SymComma
|
itemExpected = lastSym == SymComma
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// TODO Check arguments
|
|
||||||
if lastSym != SymClosedSquare {
|
if lastSym != SymClosedSquare {
|
||||||
err = scanner.Previous().ErrorExpectedGot("]")
|
err = scanner.Previous().ErrorExpectedGot("]")
|
||||||
} else {
|
} else {
|
||||||
subtree = newListTerm(args)
|
subtree = newListTerm(r, c, args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
func (parser *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
||||||
tk := scanner.Previous()
|
tk := scanner.Previous()
|
||||||
args := make([]*term, 0)
|
args := make([]*term, 0)
|
||||||
lastSym := SymUnknown
|
lastSym := SymUnknown
|
||||||
itemExpected := false
|
itemExpected := false
|
||||||
for lastSym != SymClosedRound && lastSym != SymEos {
|
for lastSym != SymClosedRound && lastSym != SymEos {
|
||||||
var subTree *ast
|
var subTree *ast
|
||||||
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil {
|
if subTree, err = parser.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil {
|
||||||
if subTree.root != nil {
|
if subTree.root != nil {
|
||||||
args = append(args, subTree.root)
|
args = append(args, subTree.root)
|
||||||
} else if itemExpected {
|
} else if itemExpected {
|
||||||
@@ -207,7 +175,6 @@ func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *t
|
|||||||
itemExpected = lastSym == SymComma
|
itemExpected = lastSym == SymComma
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// TODO Check arguments
|
|
||||||
if lastSym != SymClosedRound {
|
if lastSym != SymClosedRound {
|
||||||
err = scanner.Previous().ErrorExpectedGot(")")
|
err = scanner.Previous().ErrorExpectedGot(")")
|
||||||
} else {
|
} else {
|
||||||
@@ -217,7 +184,7 @@ func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *t
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, err error) {
|
func (parser *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, err error) {
|
||||||
tk := scanner.Next()
|
tk := scanner.Next()
|
||||||
if tk.Sym == SymError {
|
if tk.Sym == SymError {
|
||||||
err = tk.Error()
|
err = tk.Error()
|
||||||
@@ -234,20 +201,19 @@ func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, e
|
|||||||
key = tk.Value
|
key = tk.Value
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// err = tk.Errorf("expected dictionary key or closed brace, got %q", tk)
|
|
||||||
err = tk.ErrorExpectedGot("dictionary-key or }")
|
err = tk.ErrorExpectedGot("dictionary-key or }")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
func (parser *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
||||||
args := make(map[any]*term, 0)
|
args := make(map[any]*term, 0)
|
||||||
lastSym := SymUnknown
|
lastSym := SymUnknown
|
||||||
itemExpected := false
|
itemExpected := false
|
||||||
for lastSym != SymClosedBrace && lastSym != SymEos {
|
for lastSym != SymClosedBrace && lastSym != SymEos {
|
||||||
var subTree *ast
|
var subTree *ast
|
||||||
var key any
|
var key any
|
||||||
if key, err = self.parseDictKey(scanner, allowVarRef); err != nil {
|
if key, err = parser.parseDictKey(scanner, allowVarRef); err != nil {
|
||||||
break
|
break
|
||||||
} else if key == nil {
|
} else if key == nil {
|
||||||
tk := scanner.Previous()
|
tk := scanner.Previous()
|
||||||
@@ -257,10 +223,10 @@ func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedBrace); err == nil {
|
if subTree, err = parser.parseItem(scanner, allowVarRef, SymComma, SymClosedBrace); err == nil {
|
||||||
if subTree.root != nil {
|
if subTree.root != nil {
|
||||||
args[key] = subTree.root
|
args[key] = subTree.root
|
||||||
} else if key != nil {
|
} else /*if key != nil*/ {
|
||||||
prev := scanner.Previous()
|
prev := scanner.Previous()
|
||||||
err = prev.ErrorExpectedGot("dictionary-value")
|
err = prev.ErrorExpectedGot("dictionary-value")
|
||||||
break
|
break
|
||||||
@@ -272,17 +238,17 @@ func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree
|
|||||||
itemExpected = lastSym == SymComma
|
itemExpected = lastSym == SymComma
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// TODO Check arguments
|
|
||||||
if lastSym != SymClosedBrace {
|
if lastSym != SymClosedBrace {
|
||||||
err = scanner.Previous().ErrorExpectedGot("}")
|
err = scanner.Previous().ErrorExpectedGot("}")
|
||||||
} else {
|
} else {
|
||||||
subtree = newDictTerm(args)
|
subtree = newDictTerm(args)
|
||||||
|
// subtree = newMapTerm(args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaultCase bool) (caseTerm *term, err error) {
|
func (parser *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaultCase bool) (caseTerm *term, err error) {
|
||||||
var filterList *term
|
var filterList *term
|
||||||
var caseExpr *ast
|
var caseExpr *ast
|
||||||
tk := scanner.Next()
|
tk := scanner.Next()
|
||||||
@@ -293,18 +259,18 @@ func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaul
|
|||||||
err = tk.Errorf("case list in default clause")
|
err = tk.Errorf("case list in default clause")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if filterList, err = self.parseList(scanner, allowVarRef); err != nil {
|
if filterList, err = parser.parseList(scanner, false, allowVarRef); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tk = scanner.Next()
|
tk = scanner.Next()
|
||||||
startRow = tk.row
|
startRow = tk.row
|
||||||
startCol = tk.col
|
startCol = tk.col
|
||||||
} else if !defaultCase {
|
} else if !defaultCase {
|
||||||
filterList = newListTerm(make([]*term, 0))
|
filterList = newListTerm(startRow, startCol, make([]*term, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
if tk.Sym == SymOpenBrace {
|
if tk.Sym == SymOpenBrace {
|
||||||
if caseExpr, err = self.parseGeneral(scanner, true, allowVarRef, SymClosedBrace); err != nil {
|
if caseExpr, err = parser.parseGeneral(scanner, true, allowVarRef, SymClosedBrace); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -330,35 +296,51 @@ func addSelectorCase(selectorTerm, caseTerm *term) {
|
|||||||
caseTerm.parent = selectorTerm
|
caseTerm.parent = selectorTerm
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *parser) parseSelector(scanner *scanner, tree *ast, allowVarRef bool) (selectorTerm *term, err error) {
|
func (parser *parser) parseSelector(scanner *scanner, tree *ast, allowVarRef bool) (selectorTerm *term, err error) {
|
||||||
var caseTerm *term
|
var caseTerm *term
|
||||||
tk := scanner.makeToken(SymSelector, '?')
|
tk := scanner.makeToken(SymSelector, '?')
|
||||||
if selectorTerm, err = tree.addToken2(tk); err != nil {
|
if selectorTerm, err = tree.addToken2(tk); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, false); err == nil {
|
if caseTerm, err = parser.parseSelectorCase(scanner, allowVarRef, false); err == nil {
|
||||||
addSelectorCase(selectorTerm, caseTerm)
|
addSelectorCase(selectorTerm, caseTerm)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *parser) parseItem(scanner *scanner, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
|
func (parser *parser) parseItem(scanner *scanner, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
|
||||||
return self.parseGeneral(scanner, false, allowVarRef, termSymbols...)
|
return parser.parseGeneral(scanner, false, allowVarRef, termSymbols...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, err error) {
|
func (parser *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, err error) {
|
||||||
return self.parseGeneral(scanner, true, false, termSymbols...)
|
return parser.parseGeneral(scanner, true, false, termSymbols...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
|
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 areSymbolsOutOfCtx(tk *Token, ctxTerm *term, syms ...Symbol) bool {
|
||||||
|
// var areOut = false
|
||||||
|
// if ctxTerm != nil {
|
||||||
|
// areOut = tk.IsOneOf(syms)
|
||||||
|
// }
|
||||||
|
// return areOut
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (parser *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
|
||||||
var selectorTerm *term = nil
|
var selectorTerm *term = nil
|
||||||
var currentTerm *term = nil
|
var currentTerm *term = nil
|
||||||
var tk *Token
|
var tk *Token
|
||||||
tree = NewAst()
|
tree = NewAst()
|
||||||
firstToken := true
|
firstToken := true
|
||||||
lastSym := SymUnknown
|
// lastSym := SymUnknown
|
||||||
for tk = scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); tk = scanner.Next() {
|
for tk = scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); /*&& !areSymbolsOutOfCtx(tk, selectorTerm, SymColon, SymDoubleColon)*/ tk = scanner.Next() {
|
||||||
if tk.Sym == SymComment {
|
if tk.Sym == SymComment {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -367,6 +349,8 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
|||||||
if allowForest {
|
if allowForest {
|
||||||
tree.ToForest()
|
tree.ToForest()
|
||||||
firstToken = true
|
firstToken = true
|
||||||
|
currentTerm = nil
|
||||||
|
selectorTerm = nil
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
err = tk.Errorf(`unexpected token %q, expected ",", "]", or ")"`, tk.source)
|
err = tk.Errorf(`unexpected token %q, expected ",", "]", or ")"`, tk.source)
|
||||||
@@ -387,42 +371,55 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
|||||||
switch tk.Sym {
|
switch tk.Sym {
|
||||||
case SymOpenRound:
|
case SymOpenRound:
|
||||||
var subTree *ast
|
var subTree *ast
|
||||||
if subTree, err = self.parseGeneral(scanner, false, allowVarRef, SymClosedRound); err == nil {
|
if subTree, err = parser.parseGeneral(scanner, false, allowVarRef, SymClosedRound); err == nil {
|
||||||
subTree.root.priority = priValue
|
subTree.root.priority = priValue
|
||||||
err = tree.addTerm(subTree.root)
|
err = tree.addTerm(newExprTerm(subTree.root))
|
||||||
currentTerm = subTree.root
|
currentTerm = subTree.root
|
||||||
}
|
}
|
||||||
case SymFuncCall:
|
case SymFuncCall:
|
||||||
var funcCallTerm *term
|
var funcCallTerm *term
|
||||||
if funcCallTerm, err = self.parseFuncCall(scanner, allowVarRef, tk); err == nil {
|
if funcCallTerm, err = parser.parseFuncCall(scanner, allowVarRef, tk); err == nil {
|
||||||
err = tree.addTerm(funcCallTerm)
|
err = tree.addTerm(funcCallTerm)
|
||||||
currentTerm = funcCallTerm
|
currentTerm = funcCallTerm
|
||||||
}
|
}
|
||||||
case SymOpenSquare:
|
case SymOpenSquare:
|
||||||
var listTerm *term
|
var listTerm *term
|
||||||
if listTerm, err = self.parseList(scanner, allowVarRef); err == nil {
|
parsingIndeces := couldBeACollection(currentTerm)
|
||||||
|
if listTerm, err = parser.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)
|
err = tree.addTerm(listTerm)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = tree.addTerm(listTerm)
|
||||||
|
}
|
||||||
currentTerm = listTerm
|
currentTerm = listTerm
|
||||||
}
|
}
|
||||||
case SymOpenBrace:
|
case SymOpenBrace:
|
||||||
|
if currentTerm != nil && currentTerm.symbol() == SymColon {
|
||||||
|
err = currentTerm.Errorf(`selector-case outside of a selector context`)
|
||||||
|
} else {
|
||||||
var mapTerm *term
|
var mapTerm *term
|
||||||
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
|
if mapTerm, err = parser.parseDictionary(scanner, allowVarRef); err == nil {
|
||||||
err = tree.addTerm(mapTerm)
|
err = tree.addTerm(mapTerm)
|
||||||
currentTerm = 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:
|
case SymFuncDef:
|
||||||
var funcDefTerm *term
|
var funcDefTerm *term
|
||||||
if funcDefTerm, err = self.parseFuncDef(scanner); err == nil {
|
if funcDefTerm, err = parser.parseFuncDef(scanner); err == nil {
|
||||||
err = tree.addTerm(funcDefTerm)
|
err = tree.addTerm(funcDefTerm)
|
||||||
currentTerm = funcDefTerm
|
currentTerm = funcDefTerm
|
||||||
}
|
}
|
||||||
case SymDollarRound:
|
case SymDollarRound:
|
||||||
var iterDefTerm *term
|
var iterDefTerm *term
|
||||||
if iterDefTerm, err = self.parseIterDef(scanner, allowVarRef); err == nil {
|
if iterDefTerm, err = parser.parseIterDef(scanner, allowVarRef); err == nil {
|
||||||
err = tree.addTerm(iterDefTerm)
|
err = tree.addTerm(iterDefTerm)
|
||||||
currentTerm = iterDefTerm
|
currentTerm = iterDefTerm
|
||||||
}
|
}
|
||||||
@@ -433,20 +430,26 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
|||||||
currentTerm, err = tree.addToken2(tk)
|
currentTerm, err = tree.addToken2(tk)
|
||||||
}
|
}
|
||||||
case SymQuestion:
|
case SymQuestion:
|
||||||
if selectorTerm, err = self.parseSelector(scanner, tree, allowVarRef); err == nil {
|
if selectorTerm, err = parser.parseSelector(scanner, tree, allowVarRef); err == nil {
|
||||||
currentTerm = selectorTerm
|
currentTerm = selectorTerm
|
||||||
}
|
}
|
||||||
case SymColon, SymDoubleColon:
|
case SymColon, SymDoubleColon:
|
||||||
var caseTerm *term
|
var caseTerm *term
|
||||||
if selectorTerm == nil {
|
if selectorTerm != nil {
|
||||||
err = tk.Errorf("selector-case outside of a selector context")
|
if caseTerm, err = parser.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
|
||||||
} else if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
|
|
||||||
addSelectorCase(selectorTerm, caseTerm)
|
addSelectorCase(selectorTerm, caseTerm)
|
||||||
currentTerm = caseTerm
|
currentTerm = caseTerm
|
||||||
if tk.Sym == SymDoubleColon {
|
if tk.Sym == SymDoubleColon {
|
||||||
selectorTerm = nil
|
selectorTerm = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
currentTerm, err = tree.addToken2(tk)
|
||||||
|
}
|
||||||
|
if tk.IsSymbol(SymColon) {
|
||||||
|
// Colon outside a selector term acts like a separator
|
||||||
|
firstToken = true
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
currentTerm, err = tree.addToken2(tk)
|
currentTerm, err = tree.addToken2(tk)
|
||||||
}
|
}
|
||||||
@@ -455,7 +458,7 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
|||||||
selectorTerm = nil
|
selectorTerm = nil
|
||||||
|
|
||||||
}
|
}
|
||||||
lastSym = tk.Sym
|
// lastSym = tk.Sym
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = tk.Error()
|
err = tk.Error()
|
||||||
@@ -463,9 +466,9 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkPrevSymbol(lastSym, wantedSym Symbol, tk *Token) (err error) {
|
// func checkPrevSymbol(lastSym, wantedSym Symbol, tk *Token) (err error) {
|
||||||
if lastSym != wantedSym {
|
// if lastSym != wantedSym {
|
||||||
err = fmt.Errorf(`assign operator (%q) must be preceded by a variable`, tk.source)
|
// err = fmt.Errorf(`assign operator (%q) must be preceded by a variable`, tk.source)
|
||||||
}
|
// }
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|||||||
-277
@@ -1,277 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// parser_test.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGeneralParser(t *testing.T) {
|
|
||||||
inputs := []inputType{
|
|
||||||
/* 1 */ {`1+/*5*/2`, int64(3), nil},
|
|
||||||
/* 2 */ {`3 == 4`, false, nil},
|
|
||||||
/* 3 */ {`3 != 4`, true, nil},
|
|
||||||
/* 4 */ {`4 == (3-1)*(10/5)`, true, nil},
|
|
||||||
/* 5 */ {`3 <= 4`, true, nil},
|
|
||||||
/* 6 */ {`3 < 4`, true, nil},
|
|
||||||
/* 7 */ {`4 < 3`, false, nil},
|
|
||||||
/* 8 */ {`1+5 < 4`, false, nil},
|
|
||||||
/* 9 */ {`3 > 4`, false, nil},
|
|
||||||
/* 10 */ {`4 >= 4`, true, nil},
|
|
||||||
/* 11 */ {`4 > 3`, true, nil},
|
|
||||||
/* 12 */ {`1+5 > 4`, true, nil},
|
|
||||||
/* 13 */ {`true`, true, nil},
|
|
||||||
/* 14 */ {`not true`, false, nil},
|
|
||||||
/* 15 */ {`true and false`, false, nil},
|
|
||||||
/* 16 */ {`true or false`, true, nil},
|
|
||||||
/* 17 */ {`"uno" + "due"`, `unodue`, nil},
|
|
||||||
/* 18 */ {`"uno" + 2`, `uno2`, nil},
|
|
||||||
/* 19 */ {`"uno" + (2+1)`, `uno3`, nil},
|
|
||||||
/* 20 */ {`"uno" * (2+1)`, `unounouno`, nil},
|
|
||||||
/* 21 */ {"1", int64(1), nil},
|
|
||||||
/* 22 */ {"1.5", float64(1.5), nil},
|
|
||||||
/* 23 */ {"1.5*2", float64(3.0), nil},
|
|
||||||
/* 24 */ {"+1", int64(1), nil},
|
|
||||||
/* 25 */ {"-1", int64(-1), nil},
|
|
||||||
/* 26 */ {"435 + 105 * 2 - 1", int64(644), nil},
|
|
||||||
/* 27 */ {"200 / 2 - 1", int64(99), nil},
|
|
||||||
/* 28 */ {"(1+1)", int64(2), nil},
|
|
||||||
/* 29 */ {"-(1+1)", int64(-2), nil},
|
|
||||||
/* 30 */ {"-(-2+1)", int64(1), nil},
|
|
||||||
/* 31 */ {"(1+1)*5", int64(10), nil},
|
|
||||||
/* 32 */ {"200 / (1+1) - 1", int64(99), nil},
|
|
||||||
/* 33 */ {`1+(1+(1+(1)+1)+1)+1`, int64(7), nil},
|
|
||||||
/* 34 */ {`(((1)))`, int64(1), nil},
|
|
||||||
/* 35 */ {`var2="abc"; "uno_" + var2`, `uno_abc`, nil},
|
|
||||||
/* 36 */ {`0 || 0.0 && "hello"`, false, nil},
|
|
||||||
/* 37 */ {`"s" + true`, nil, errors.New(`[1:6] left operand 's' [string] and right operand 'true' [bool] are not compatible with operator "+"`)},
|
|
||||||
/* 38 */ {`+false`, nil, errors.New(`[1:2] prefix/postfix operator "+" do not support operand 'false' [bool]`)},
|
|
||||||
/* 39 */ {`false // very simple expression`, false, nil},
|
|
||||||
/* 40 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two non-nil operands, got 1`)},
|
|
||||||
/* 41 */ {"", nil, nil},
|
|
||||||
/* 42 */ {"4!", int64(24), nil},
|
|
||||||
/* 43 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)},
|
|
||||||
/* 44 */ {"-4!", int64(-24), nil},
|
|
||||||
/* 45 */ {"1.5 < 7", true, nil},
|
|
||||||
/* 46 */ {"1.5 > 7", false, nil},
|
|
||||||
/* 47 */ {"1.5 <= 7", true, nil},
|
|
||||||
/* 48 */ {"1.5 >= 7", false, nil},
|
|
||||||
/* 49 */ {"1.5 != 7", true, nil},
|
|
||||||
/* 50 */ {"1.5 == 7", false, nil},
|
|
||||||
/* 51 */ {`"1.5" < "7"`, true, nil},
|
|
||||||
/* 52 */ {`"1.5" > "7"`, false, nil},
|
|
||||||
/* 53 */ {`"1.5" == "7"`, false, nil},
|
|
||||||
/* 54 */ {`"1.5" != "7"`, true, nil},
|
|
||||||
/* 55 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two non-nil operands, got 1`)},
|
|
||||||
/* 56 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two non-nil operands, got 1`)},
|
|
||||||
/* 57 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two non-nil operands, got 1`)},
|
|
||||||
/* 58 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two non-nil operands, got 1`)},
|
|
||||||
/* 59 */ {"1.5 != ", nil, errors.New(`[1:6] infix operator "!=" requires two non-nil operands, got 1`)},
|
|
||||||
/* 60 */ {"1.5 == ", nil, errors.New(`[1:6] infix operator "==" requires two non-nil operands, got 1`)},
|
|
||||||
/* 61 */ {`"1.5" < `, nil, errors.New(`[1:8] infix operator "<" requires two non-nil operands, got 1`)},
|
|
||||||
/* 62 */ {`"1.5" > `, nil, errors.New(`[1:8] infix operator ">" requires two non-nil operands, got 1`)},
|
|
||||||
/* 63 */ {`"1.5" == `, nil, errors.New(`[1:8] infix operator "==" requires two non-nil operands, got 1`)},
|
|
||||||
/* 64 */ {`"1.5" != `, nil, errors.New(`[1:8] infix operator "!=" requires two non-nil operands, got 1`)},
|
|
||||||
/* 65 */ {"+1.5", float64(1.5), nil},
|
|
||||||
/* 66 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one not nil operand`)},
|
|
||||||
/* 67 */ {"4 / 0", nil, errors.New(`division by zero`)},
|
|
||||||
/* 68 */ {"4.0 / 0", nil, errors.New(`division by zero`)},
|
|
||||||
/* 69 */ {"4.0 / \n2", float64(2.0), nil},
|
|
||||||
/* 70 */ {`123`, int64(123), nil},
|
|
||||||
/* 71 */ {`1.`, float64(1.0), nil},
|
|
||||||
/* 72 */ {`1.E-2`, float64(0.01), nil},
|
|
||||||
/* 73 */ {`1E2`, float64(100), nil},
|
|
||||||
/* 74 */ {`1 / 2`, int64(0), nil},
|
|
||||||
/* 75 */ {`1.0 / 2`, float64(0.5), nil},
|
|
||||||
/* 76 */ {`1 ./ 2`, float64(0.5), nil},
|
|
||||||
/* 77 */ {`5 % 2`, int64(1), nil},
|
|
||||||
/* 78 */ {`5 % (-2)`, int64(1), nil},
|
|
||||||
/* 79 */ {`-5 % 2`, int64(-1), nil},
|
|
||||||
/* 80 */ {`5 % 2.0`, nil, errors.New(`[1:4] left operand '5' [int64] and right operand '2' [float64] are not compatible with operator "%"`)},
|
|
||||||
/* 81 */ {`"a" < "b" AND NOT (2 < 1)`, true, nil},
|
|
||||||
/* 82 */ {`"a" < "b" AND NOT (2 == 1)`, true, nil},
|
|
||||||
/* 83 */ {`"a" < "b" AND ~ 2 == 1`, true, nil},
|
|
||||||
/* 84 */ {`~ 2 > 1`, false, nil},
|
|
||||||
/* 85 */ {`~ true && true`, false, nil},
|
|
||||||
/* 86 */ {`~ false || true`, true, nil},
|
|
||||||
/* 87 */ {`false but true`, true, nil},
|
|
||||||
/* 88 */ {`2+3 but 5*2`, int64(10), nil},
|
|
||||||
/* 89 */ {`x=2`, int64(2), nil},
|
|
||||||
/* 90 */ {`x=2 but x*10`, int64(20), nil},
|
|
||||||
/* 91 */ {`false and true`, false, nil},
|
|
||||||
/* 92 */ {`false and (x==2)`, false, nil},
|
|
||||||
/* 93 */ {`false and (x=2 but x==2) or x==2`, nil, errors.New(`undefined variable or function "x"`)},
|
|
||||||
/* 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`)},
|
|
||||||
/* 99 */ {`2+(a=5)`, int64(7), nil},
|
|
||||||
/* 100 */ {`x ?? "default"`, "default", nil},
|
|
||||||
/* 101 */ {`x="hello"; x ?? "default"`, "hello", nil},
|
|
||||||
/* 102 */ {`y=x ?? func(){4}; y()`, int64(4), nil},
|
|
||||||
/* 103 */ {`x ?= "default"; x`, "default", nil},
|
|
||||||
/* 104 */ {`x="hello"; x ?= "default"; x`, "hello", nil},
|
|
||||||
/* 105 */ {`1 ? {"a"} : {"b"}`, "b", nil},
|
|
||||||
/* 106 */ {`10 ? {"a"} : {"b"} :: {"c"}`, "c", nil},
|
|
||||||
/* 107 */ {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", nil},
|
|
||||||
/* 108 */ {`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}`, nil, errors.New(`[1:34] case list in default clause`)},
|
|
||||||
/* 109 */ {`10 ? {"a"} :[10] {x="b" but x} :: {"c"}`, "b", nil},
|
|
||||||
/* 110 */ {`10 ? {"a"} :[10] {x="b"; x} :: {"c"}`, "b", nil},
|
|
||||||
/* 111 */ {`10 ? {"a"} : {"b"}`, nil, errors.New(`[1:3] no case catches the value (10) of the selection expression`)},
|
|
||||||
/* 112 */ {`10 ? {"a"} :: {"b"} : {"c"}`, nil, errors.New(`[1:22] selector-case outside of a selector context`)},
|
|
||||||
/* 113 */ {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
|
|
||||||
/* 114 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
|
|
||||||
/* 115 */ {`nil`, nil, nil},
|
|
||||||
/* 116 */ {`null`, nil, errors.New(`undefined variable or function "null"`)},
|
|
||||||
/* 117 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)},
|
|
||||||
/* 118 */ {`{"key":}`, nil, errors.New(`[1:9] expected "dictionary-value", got "}"`)},
|
|
||||||
/* 119 */ {`{}`, map[any]any{}, 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},
|
|
||||||
}
|
|
||||||
check_env_expr_path := 113
|
|
||||||
|
|
||||||
succeeded := 0
|
|
||||||
failed := 0
|
|
||||||
|
|
||||||
// inputs1 := []inputType{
|
|
||||||
// /* 159 */ {`include "file-reader.expr"; it=$(ds,"int.list"); mul(it)`, int64(12000), nil},
|
|
||||||
// }
|
|
||||||
|
|
||||||
for i, input := range inputs {
|
|
||||||
var expr Expr
|
|
||||||
var gotResult any
|
|
||||||
var gotErr error
|
|
||||||
|
|
||||||
ctx := NewSimpleFuncStore()
|
|
||||||
// ctx.SetVar("var1", int64(123))
|
|
||||||
// ctx.SetVar("var2", "abc")
|
|
||||||
// ImportMathFuncs(ctx)
|
|
||||||
// ImportImportFuncs(ctx)
|
|
||||||
parser := NewParser(ctx)
|
|
||||||
|
|
||||||
logTest(t, i+1, "General", 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if good {
|
|
||||||
succeeded++
|
|
||||||
} else {
|
|
||||||
failed++
|
|
||||||
if i+1 == check_env_expr_path {
|
|
||||||
t.Logf(`NOTICE: Test nr %d requires EXPR_PATH="."`, check_env_expr_path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.Logf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed)
|
|
||||||
}
|
|
||||||
|
|
||||||
type inputType struct {
|
|
||||||
source string
|
|
||||||
wantResult any
|
|
||||||
wantErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
func parserTest(t *testing.T, section string, inputs []inputType) {
|
|
||||||
|
|
||||||
succeeded := 0
|
|
||||||
failed := 0
|
|
||||||
|
|
||||||
// inputs1 := []inputType{
|
|
||||||
// /* 159 */ {`include "file-reader.expr"; it=$(ds,"int.list"); mul(it)`, int64(12000), nil},
|
|
||||||
// }
|
|
||||||
|
|
||||||
for i, input := range inputs {
|
|
||||||
var expr Expr
|
|
||||||
var gotResult any
|
|
||||||
var gotErr error
|
|
||||||
|
|
||||||
ctx := NewSimpleFuncStore()
|
|
||||||
// ctx.SetVar("var1", int64(123))
|
|
||||||
// ctx.SetVar("var2", "abc")
|
|
||||||
// ImportMathFuncs(ctx)
|
|
||||||
// ImportImportFuncs(ctx)
|
|
||||||
parser := NewParser(ctx)
|
|
||||||
|
|
||||||
logTest(t, i+1, "Iterator", 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if good {
|
|
||||||
succeeded++
|
|
||||||
} else {
|
|
||||||
failed++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
|
||||||
}
|
|
||||||
|
|
||||||
func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
|
|
||||||
if wantErr == nil {
|
|
||||||
t.Logf("[+]%s nr %3d -- %q --> %v", section, n, source, wantResult)
|
|
||||||
} else {
|
|
||||||
t.Logf("[-]%s nr %3d -- %q --> %v", section, n, source, wantErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+107
@@ -0,0 +1,107 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// plugin.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"plugin"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pluginRegister map[string]*plugin.Plugin
|
||||||
|
|
||||||
|
func registerPlugin(name string, p *plugin.Plugin) (err error) {
|
||||||
|
if pluginExists(name) {
|
||||||
|
err = fmt.Errorf("plugin %q already loaded", name)
|
||||||
|
} else {
|
||||||
|
pluginRegister[name] = p
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func pluginExists(name string) (exists bool) {
|
||||||
|
_, exists = pluginRegister[name]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePluginName(name string) (decorated string) {
|
||||||
|
var template string
|
||||||
|
if execName, err := os.Executable(); err != nil || !strings.Contains(execName, "debug") {
|
||||||
|
template = "expr-%s-plugin.so"
|
||||||
|
} else {
|
||||||
|
template = "expr-%s-plugin.so.debug"
|
||||||
|
}
|
||||||
|
decorated = fmt.Sprintf(template, name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func importPlugin( /*ctx ExprContext,*/ dirList []string, name string) (err error) {
|
||||||
|
var filePath string
|
||||||
|
var p *plugin.Plugin
|
||||||
|
var sym plugin.Symbol
|
||||||
|
var moduleName string
|
||||||
|
var importFunc func(ExprContext)
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
decoratedName := makePluginName(name)
|
||||||
|
|
||||||
|
if filePath, err = makeFilepath(decoratedName, dirList); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if p, err = plugin.Open(filePath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if sym, err = p.Lookup("MODULE_NAME"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if moduleName = *sym.(*string); moduleName == "" {
|
||||||
|
err = fmt.Errorf("plugin %q does not provide a valid module name", decoratedName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if sym, err = p.Lookup("DEPENDENCIES"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if deps := *sym.(*[]string); len(deps) > 0 {
|
||||||
|
// var count int
|
||||||
|
if err = loadModules(dirList, deps); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sym, err = p.Lookup("ImportFuncs"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if importFunc, ok = sym.(func(ExprContext)); !ok {
|
||||||
|
err = fmt.Errorf("plugin %q does not provide a valid import function", decoratedName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
registerPlugin(moduleName, p)
|
||||||
|
importFunc(globalCtx)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadModules(dirList []string, moduleNames []string) (err error) {
|
||||||
|
for _, name := range moduleNames {
|
||||||
|
if err1 := importPlugin(dirList, name); err1 != nil {
|
||||||
|
if !ImportInContext(name) {
|
||||||
|
err = err1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pluginRegister = make(map[string]*plugin.Plugin)
|
||||||
|
}
|
||||||
+215
-181
@@ -49,242 +49,257 @@ func DefaultTranslations() map[Symbol]Symbol {
|
|||||||
// return self.current
|
// return self.current
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func (self *scanner) readChar() (ch byte, err error) {
|
func (scanner *scanner) readChar() (ch byte, err error) {
|
||||||
if ch, err = self.stream.ReadByte(); err == nil {
|
if ch, err = scanner.stream.ReadByte(); err == nil {
|
||||||
if ch == '\n' {
|
if ch == '\n' {
|
||||||
self.row++
|
scanner.row++
|
||||||
self.column = 0
|
scanner.column = 0
|
||||||
} else {
|
} else {
|
||||||
self.column++
|
scanner.column++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) unreadChar() (err error) {
|
func (scanner *scanner) unreadChar() (err error) {
|
||||||
if err = self.stream.UnreadByte(); err == nil {
|
if err = scanner.stream.UnreadByte(); err == nil {
|
||||||
if self.column--; self.column == 0 {
|
if scanner.column--; scanner.column == 0 {
|
||||||
if self.row--; self.row == 0 {
|
if scanner.row--; scanner.row == 0 {
|
||||||
err = errors.New("unread beyond the stream boundary")
|
err = errors.New("unread beyond the stream boundary")
|
||||||
} else {
|
} else {
|
||||||
self.column = 1
|
scanner.column = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) Previous() *Token {
|
func (scanner *scanner) lastPos() (r, c int) {
|
||||||
return self.prev
|
if scanner.prev != nil {
|
||||||
|
r = scanner.prev.row
|
||||||
|
c = scanner.prev.col
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) Next() (tk *Token) {
|
func (scanner *scanner) Previous() *Token {
|
||||||
self.prev = self.current
|
return scanner.prev
|
||||||
tk = self.current
|
}
|
||||||
self.current = self.fetchNextToken()
|
|
||||||
|
func (scanner *scanner) Next() (tk *Token) {
|
||||||
|
scanner.prev = scanner.current
|
||||||
|
tk = scanner.current
|
||||||
|
scanner.current = scanner.fetchNextToken()
|
||||||
return tk
|
return tk
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) fetchNextToken() (tk *Token) {
|
func (scanner *scanner) fetchNextToken() (tk *Token) {
|
||||||
var ch byte
|
var ch byte
|
||||||
if err := self.skipBlanks(); err != nil {
|
if err := scanner.skipBlanks(); err != nil {
|
||||||
return self.makeErrorToken(err)
|
return scanner.makeErrorToken(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
escape := false
|
escape := false
|
||||||
for {
|
for {
|
||||||
ch, _ = self.readChar()
|
ch, _ = scanner.readChar()
|
||||||
switch ch {
|
switch ch {
|
||||||
case '+':
|
case '+':
|
||||||
if next, _ := self.peek(); next == '+' {
|
if next, _ := scanner.peek(); next == '+' {
|
||||||
tk = self.moveOn(SymDoublePlus, ch, next)
|
tk = scanner.moveOn(SymDoublePlus, ch, next)
|
||||||
} else if next == '=' {
|
} else if next == '=' {
|
||||||
tk = self.moveOn(SymPlusEqual, ch, next)
|
tk = scanner.moveOn(SymPlusEqual, ch, next)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymPlus, ch)
|
tk = scanner.makeToken(SymPlus, ch)
|
||||||
}
|
}
|
||||||
case '-':
|
case '-':
|
||||||
if next, _ := self.peek(); next == '-' {
|
if next, _ := scanner.peek(); next == '-' {
|
||||||
tk = self.moveOn(SymDoubleMinus, ch, next)
|
tk = scanner.moveOn(SymDoubleMinus, ch, next)
|
||||||
} else if next == '=' {
|
} else if next == '=' {
|
||||||
tk = self.moveOn(SymMinusEqual, ch, next)
|
tk = scanner.moveOn(SymMinusEqual, ch, next)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymMinus, ch)
|
tk = scanner.makeToken(SymMinus, ch)
|
||||||
}
|
}
|
||||||
case '*':
|
case '*':
|
||||||
if next, _ := self.peek(); next == '*' {
|
if next, _ := scanner.peek(); next == '*' {
|
||||||
tk = self.moveOn(SymDoubleStar, ch, next)
|
tk = scanner.moveOn(SymDoubleStar, ch, next)
|
||||||
// } else if next == '/' {
|
// } else if next == '/' {
|
||||||
// tk = self.moveOn(SymClosedComment, ch, next)
|
// tk = self.moveOn(SymClosedComment, ch, next)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymStar, ch)
|
tk = scanner.makeToken(SymStar, ch)
|
||||||
}
|
}
|
||||||
case '/':
|
case '/':
|
||||||
if next, _ := self.peek(); next == '*' {
|
if next, _ := scanner.peek(); next == '*' {
|
||||||
self.readChar()
|
scanner.readChar()
|
||||||
tk = self.fetchBlockComment()
|
tk = scanner.fetchBlockComment()
|
||||||
} else if next == '/' {
|
} else if next == '/' {
|
||||||
self.readChar()
|
scanner.readChar()
|
||||||
tk = self.fetchOnLineComment()
|
tk = scanner.fetchOnLineComment()
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymSlash, ch)
|
tk = scanner.makeToken(SymSlash, ch)
|
||||||
}
|
}
|
||||||
case '\\':
|
case '\\':
|
||||||
if escape {
|
if escape {
|
||||||
tk = self.makeToken(SymBackSlash, ch)
|
tk = scanner.makeToken(SymBackSlash, ch)
|
||||||
escape = false
|
escape = false
|
||||||
} else {
|
} else {
|
||||||
escape = true
|
escape = true
|
||||||
}
|
}
|
||||||
case '|':
|
case '|':
|
||||||
if next, _ := self.peek(); next == '|' {
|
if next, _ := scanner.peek(); next == '|' {
|
||||||
tk = self.moveOn(SymDoubleVertBar, ch, next)
|
tk = scanner.moveOn(SymDoubleVertBar, ch, next)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymVertBar, ch)
|
tk = scanner.makeToken(SymVertBar, ch)
|
||||||
}
|
}
|
||||||
case ',':
|
case ',':
|
||||||
tk = self.makeToken(SymComma, ch)
|
tk = scanner.makeToken(SymComma, ch)
|
||||||
case '^':
|
case '^':
|
||||||
tk = self.makeToken(SymCaret, ch)
|
tk = scanner.makeToken(SymCaret, ch)
|
||||||
case ':':
|
case ':':
|
||||||
if next, _ := self.peek(); next == ':' {
|
if next, _ := scanner.peek(); next == ':' {
|
||||||
tk = self.moveOn(SymDoubleColon, ch, next)
|
tk = scanner.moveOn(SymDoubleColon, ch, next)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymColon, ch)
|
tk = scanner.makeToken(SymColon, ch)
|
||||||
}
|
}
|
||||||
case ';':
|
case ';':
|
||||||
tk = self.makeToken(SymSemiColon, ch)
|
tk = scanner.makeToken(SymSemiColon, ch)
|
||||||
case '.':
|
case '.':
|
||||||
//if next, _ := self.peek(); next >= '0' && next <= '9' {
|
//if next, _ := self.peek(); next >= '0' && next <= '9' {
|
||||||
// tk = self.parseNumber(ch)
|
// tk = self.parseNumber(ch)
|
||||||
//} else if next == '/' {
|
//} else if next == '/' {
|
||||||
if next, _ := self.peek(); next == '/' {
|
if next, _ := scanner.peek(); next == '/' {
|
||||||
tk = self.moveOn(SymDotSlash, ch, next)
|
tk = scanner.moveOn(SymDotSlash, ch, next)
|
||||||
} else if next == '.' {
|
} else if next == '.' {
|
||||||
if next1, _ := self.peek(); next1 == '.' {
|
if next1, _ := scanner.peek(); next1 == '.' {
|
||||||
tk = self.moveOn(SymTripleDot, ch, next, next1)
|
tk = scanner.moveOn(SymTripleDot, ch, next, next1)
|
||||||
} else {
|
} else {
|
||||||
tk = self.moveOn(SymDoubleDot, ch, next)
|
tk = scanner.moveOn(SymDoubleDot, ch, next)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymDot, ch)
|
tk = scanner.makeToken(SymDot, ch)
|
||||||
}
|
}
|
||||||
case '\'':
|
case '\'':
|
||||||
tk = self.makeToken(SymQuote, ch)
|
|
||||||
case '"':
|
|
||||||
if escape {
|
if escape {
|
||||||
tk = self.makeToken(SymDoubleQuote, ch)
|
tk = scanner.makeToken(SymQuote, ch)
|
||||||
escape = false
|
escape = false
|
||||||
} else {
|
} else {
|
||||||
tk = self.fetchString()
|
tk = scanner.fetchString(ch)
|
||||||
|
}
|
||||||
|
case '"':
|
||||||
|
if escape {
|
||||||
|
tk = scanner.makeToken(SymDoubleQuote, ch)
|
||||||
|
escape = false
|
||||||
|
} else {
|
||||||
|
tk = scanner.fetchString(ch)
|
||||||
}
|
}
|
||||||
case '`':
|
case '`':
|
||||||
tk = self.makeToken(SymBackTick, ch)
|
tk = scanner.makeToken(SymBackTick, ch)
|
||||||
case '!':
|
case '!':
|
||||||
if next, _ := self.peek(); next == '=' {
|
if next, _ := scanner.peek(); next == '=' {
|
||||||
tk = self.moveOn(SymNotEqual, ch, next)
|
tk = scanner.moveOn(SymNotEqual, ch, next)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymExclamation, ch)
|
tk = scanner.makeToken(SymExclamation, ch)
|
||||||
}
|
}
|
||||||
case '?':
|
case '?':
|
||||||
if next, _ := self.peek(); next == '?' {
|
if next, _ := scanner.peek(); next == '?' {
|
||||||
tk = self.moveOn(SymDoubleQuestion, ch, next)
|
tk = scanner.moveOn(SymDoubleQuestion, ch, next)
|
||||||
} else if next, _ := self.peek(); next == '=' {
|
} else if next == '=' {
|
||||||
tk = self.moveOn(SymQuestionEqual, ch, next)
|
tk = scanner.moveOn(SymQuestionEqual, ch, next)
|
||||||
|
} else if next == '!' {
|
||||||
|
tk = scanner.moveOn(SymQuestionExclam, ch, next)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymQuestion, ch)
|
tk = scanner.makeToken(SymQuestion, ch)
|
||||||
}
|
}
|
||||||
case '&':
|
case '&':
|
||||||
if next, _ := self.peek(); next == '&' {
|
if next, _ := scanner.peek(); next == '&' {
|
||||||
tk = self.moveOn(SymDoubleAmpersand, ch, next)
|
tk = scanner.moveOn(SymDoubleAmpersand, ch, next)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymAmpersand, ch)
|
tk = scanner.makeToken(SymAmpersand, ch)
|
||||||
}
|
}
|
||||||
case '%':
|
case '%':
|
||||||
tk = self.makeToken(SymPercent, ch)
|
tk = scanner.makeToken(SymPercent, ch)
|
||||||
case '#':
|
case '#':
|
||||||
tk = self.makeToken(SymHash, ch)
|
tk = scanner.makeToken(SymHash, ch)
|
||||||
case '@':
|
case '@':
|
||||||
if next, _ := self.peek(); (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z') {
|
if next, _ := scanner.peek(); (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z') {
|
||||||
self.readChar()
|
scanner.readChar()
|
||||||
if tk = self.fetchIdentifier(next); tk.Sym == SymIdentifier {
|
if tk = scanner.fetchIdentifier(next); tk.Sym == SymIdentifier {
|
||||||
//tk.Sym = SymIdRef
|
//tk.Sym = SymIdRef
|
||||||
tk.source = "@" + tk.source
|
tk.source = "@" + tk.source
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeErrorToken(fmt.Errorf("invalid variable reference %q", tk.source))
|
tk = scanner.makeErrorToken(fmt.Errorf("invalid variable reference %q", tk.source))
|
||||||
}
|
}
|
||||||
} else if next == '@' {
|
} else if next == '@' {
|
||||||
tk = self.moveOn(SymDoubleAt, ch, next)
|
tk = scanner.moveOn(SymDoubleAt, ch, next)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymAt, ch)
|
tk = scanner.makeToken(SymAt, ch)
|
||||||
}
|
}
|
||||||
case '_':
|
case '_':
|
||||||
tk = self.makeToken(SymUndescore, ch)
|
tk = scanner.makeToken(SymUndescore, ch)
|
||||||
case '=':
|
case '=':
|
||||||
if next, _ := self.peek(); next == '=' {
|
if next, _ := scanner.peek(); next == '=' {
|
||||||
tk = self.moveOn(SymDoubleEqual, ch, next)
|
tk = scanner.moveOn(SymDoubleEqual, ch, next)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymEqual, ch)
|
tk = scanner.makeToken(SymEqual, ch)
|
||||||
}
|
}
|
||||||
case '<':
|
case '<':
|
||||||
if next, _ := self.peek(); next == '=' {
|
if next, _ := scanner.peek(); next == '=' {
|
||||||
tk = self.moveOn(SymLessOrEqual, ch, next)
|
tk = scanner.moveOn(SymLessOrEqual, ch, next)
|
||||||
} else if next == '<' {
|
} else if next == '<' {
|
||||||
tk = self.moveOn(SymAppend, ch, next)
|
tk = scanner.moveOn(SymAppend, ch, next)
|
||||||
} else if next == '>' {
|
} else if next == '>' {
|
||||||
tk = self.moveOn(SymLessGreater, ch, next)
|
tk = scanner.moveOn(SymLessGreater, ch, next)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymLess, ch)
|
tk = scanner.makeToken(SymLess, ch)
|
||||||
}
|
}
|
||||||
case '>':
|
case '>':
|
||||||
if next, _ := self.peek(); next == '=' {
|
if next, _ := scanner.peek(); next == '=' {
|
||||||
tk = self.moveOn(SymGreaterOrEqual, ch, next)
|
tk = scanner.moveOn(SymGreaterOrEqual, ch, next)
|
||||||
} else if next == '>' {
|
} else if next == '>' {
|
||||||
tk = self.moveOn(SymInsert, ch, next)
|
tk = scanner.moveOn(SymInsert, ch, next)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymGreater, ch)
|
tk = scanner.makeToken(SymGreater, ch)
|
||||||
}
|
}
|
||||||
case '$':
|
case '$':
|
||||||
if next, _ := self.peek(); next == '(' {
|
if next, _ := scanner.peek(); next == '(' {
|
||||||
tk = self.moveOn(SymDollarRound, ch, next)
|
tk = scanner.moveOn(SymDollarRound, ch, next)
|
||||||
tk.source += ")"
|
tk.source += ")"
|
||||||
} else if next == '$' {
|
} else if next == '$' {
|
||||||
tk = self.moveOn(SymDoubleDollar, ch, next)
|
tk = scanner.moveOn(SymDoubleDollar, ch, next)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymDollar, ch)
|
tk = scanner.makeToken(SymDollar, ch)
|
||||||
}
|
}
|
||||||
case '(':
|
case '(':
|
||||||
if next, _ := self.peek(); next == ')' {
|
// if next, _ := scanner.peek(); next == ')' {
|
||||||
tk = self.moveOn(SymOpenClosedRound, ch, next)
|
// tk = scanner.moveOn(SymOpenClosedRound, ch, next)
|
||||||
} else {
|
// } else {
|
||||||
tk = self.makeToken(SymOpenRound, ch)
|
tk = scanner.makeToken(SymOpenRound, ch)
|
||||||
}
|
// }
|
||||||
case ')':
|
case ')':
|
||||||
tk = self.makeToken(SymClosedRound, ch)
|
tk = scanner.makeToken(SymClosedRound, ch)
|
||||||
case '[':
|
case '[':
|
||||||
tk = self.makeToken(SymOpenSquare, ch)
|
tk = scanner.makeToken(SymOpenSquare, ch)
|
||||||
case ']':
|
case ']':
|
||||||
tk = self.makeToken(SymClosedSquare, ch)
|
tk = scanner.makeToken(SymClosedSquare, ch)
|
||||||
case '{':
|
case '{':
|
||||||
tk = self.makeToken(SymOpenBrace, ch)
|
tk = scanner.makeToken(SymOpenBrace, ch)
|
||||||
case '}':
|
case '}':
|
||||||
tk = self.makeToken(SymClosedBrace, ch)
|
tk = scanner.makeToken(SymClosedBrace, ch)
|
||||||
case '~':
|
case '~':
|
||||||
tk = self.makeToken(SymTilde, ch)
|
tk = scanner.makeToken(SymTilde, ch)
|
||||||
case 0:
|
case 0:
|
||||||
if escape {
|
if escape {
|
||||||
tk = self.makeErrorToken(errors.New("incomplete escape sequence"))
|
tk = scanner.makeErrorToken(errors.New("incomplete escape sequence"))
|
||||||
}
|
}
|
||||||
escape = false
|
escape = false
|
||||||
default:
|
default:
|
||||||
if /*ch == '_' ||*/ (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') {
|
if /*ch == '_' ||*/ (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') {
|
||||||
if tk = self.fetchIdentifier(ch); tk.Sym == SymKwFunc {
|
if tk = scanner.fetchIdentifier(ch); tk.Sym == SymKwFunc {
|
||||||
if next, _ := self.peek(); next == '(' {
|
if next, _ := scanner.peek(); next == '(' {
|
||||||
tk = self.moveOn(SymFuncDef, ch, next)
|
tk = scanner.moveOn(SymFuncDef, ch, next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if ch >= '0' && ch <= '9' {
|
} else if ch >= '0' && ch <= '9' {
|
||||||
tk = self.parseNumber(ch)
|
tk = scanner.parseNumber(ch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !escape {
|
if !escape {
|
||||||
@@ -292,14 +307,14 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if tk == nil {
|
if tk == nil {
|
||||||
tk = NewErrorToken(self.row, self.column, fmt.Errorf("unknown symbol '%c'", ch))
|
tk = NewErrorToken(scanner.row, scanner.column, fmt.Errorf("unknown symbol '%c'", ch))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) sync(err error) error {
|
func (scanner *scanner) sync(err error) error {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = self.unreadChar()
|
err = scanner.unreadChar()
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -320,32 +335,32 @@ func isHexDigit(ch byte) bool {
|
|||||||
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
|
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) initBase(sb *strings.Builder, currentFirstCh byte) (firstCh byte, numBase int, digitFunc func(byte) bool, err error) {
|
func (scanner *scanner) initBase(currentFirstCh byte) (firstCh byte, numBase int, digitFunc func(byte) bool, err error) {
|
||||||
var ch byte
|
var ch byte
|
||||||
var digitType string
|
var digitType string
|
||||||
firstCh = currentFirstCh
|
firstCh = currentFirstCh
|
||||||
digitFunc = isDecimalDigit
|
digitFunc = isDecimalDigit
|
||||||
numBase = 10
|
numBase = 10
|
||||||
|
|
||||||
if ch, err = self.peek(); err == nil {
|
if ch, err = scanner.peek(); err == nil {
|
||||||
if ch == 'b' || ch == 'B' {
|
if ch == 'b' || ch == 'B' {
|
||||||
numBase = 2
|
numBase = 2
|
||||||
digitType = "binary"
|
digitType = "binary"
|
||||||
self.readChar()
|
scanner.readChar()
|
||||||
digitFunc = isBinaryDigit
|
digitFunc = isBinaryDigit
|
||||||
firstCh, err = self.readChar()
|
firstCh, err = scanner.readChar()
|
||||||
} else if ch == 'o' || ch == 'O' {
|
} else if ch == 'o' || ch == 'O' {
|
||||||
numBase = 8
|
numBase = 8
|
||||||
digitType = "octal"
|
digitType = "octal"
|
||||||
self.readChar()
|
scanner.readChar()
|
||||||
digitFunc = isOctalDigit
|
digitFunc = isOctalDigit
|
||||||
firstCh, err = self.readChar()
|
firstCh, err = scanner.readChar()
|
||||||
} else if ch == 'x' || ch == 'X' {
|
} else if ch == 'x' || ch == 'X' {
|
||||||
numBase = 16
|
numBase = 16
|
||||||
digitType = "hex"
|
digitType = "hex"
|
||||||
self.readChar()
|
scanner.readChar()
|
||||||
digitFunc = isHexDigit
|
digitFunc = isHexDigit
|
||||||
firstCh, err = self.readChar()
|
firstCh, err = scanner.readChar()
|
||||||
}
|
}
|
||||||
if err == nil && !digitFunc(firstCh) {
|
if err == nil && !digitFunc(firstCh) {
|
||||||
if len(digitType) == 0 {
|
if len(digitType) == 0 {
|
||||||
@@ -359,7 +374,7 @@ func (self *scanner) initBase(sb *strings.Builder, currentFirstCh byte) (firstCh
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
|
func (scanner *scanner) parseNumber(firstCh byte) (tk *Token) {
|
||||||
var err error
|
var err error
|
||||||
var ch byte
|
var ch byte
|
||||||
var sym Symbol = SymInteger
|
var sym Symbol = SymInteger
|
||||||
@@ -368,9 +383,9 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
|
|||||||
var numBase = 10
|
var numBase = 10
|
||||||
|
|
||||||
if firstCh == '0' {
|
if firstCh == '0' {
|
||||||
firstCh, numBase, isDigit, err = self.initBase(&sb, firstCh)
|
firstCh, numBase, isDigit, err = scanner.initBase(firstCh)
|
||||||
}
|
}
|
||||||
for ch = firstCh; err == nil && isDigit(ch); ch, err = self.readChar() {
|
for ch = firstCh; err == nil && isDigit(ch); ch, err = scanner.readChar() {
|
||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,77 +393,96 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
|
|||||||
if err == nil && ch == '.' {
|
if err == nil && ch == '.' {
|
||||||
sym = SymFloat
|
sym = SymFloat
|
||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
ch, err = self.readChar()
|
ch, err = scanner.readChar()
|
||||||
if ch >= '0' && ch <= '9' {
|
if ch >= '0' && ch <= '9' {
|
||||||
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
|
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = scanner.readChar() {
|
||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == nil && (ch == 'e' || ch == 'E') {
|
if err == nil {
|
||||||
|
if ch == 'e' || ch == 'E' {
|
||||||
sym = SymFloat
|
sym = SymFloat
|
||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
if ch, err = self.readChar(); err == nil {
|
if ch, err = scanner.readChar(); err == nil {
|
||||||
if ch == '+' || ch == '-' {
|
if ch == '+' || ch == '-' {
|
||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
ch, err = self.readChar()
|
ch, err = scanner.readChar()
|
||||||
}
|
}
|
||||||
if ch >= '0' && ch <= '9' {
|
if ch >= '0' && ch <= '9' {
|
||||||
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
|
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = scanner.readChar() {
|
||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = errors.New("expected integer exponent")
|
err = fmt.Errorf("[%d:%d] expected integer exponent, got %c", scanner.row, scanner.column, ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ch == '(' {
|
||||||
|
sym = SymFraction
|
||||||
|
sb.WriteByte(ch)
|
||||||
|
ch, err = scanner.readChar()
|
||||||
|
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = scanner.readChar() {
|
||||||
|
sb.WriteByte(ch)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
if ch != ')' {
|
||||||
|
err = fmt.Errorf("[%d:%d] expected ')', got '%c'", scanner.row, scanner.column, ch)
|
||||||
|
} else {
|
||||||
|
sb.WriteByte(ch)
|
||||||
|
_, err = scanner.readChar()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
tk = self.makeErrorToken(err)
|
tk = scanner.makeErrorToken(err)
|
||||||
} else {
|
} else {
|
||||||
var value any
|
var value any
|
||||||
err = self.sync(err)
|
err = scanner.sync(err) // TODO: Check this function
|
||||||
txt := sb.String()
|
txt := sb.String()
|
||||||
if sym == SymFloat {
|
if sym == SymFloat {
|
||||||
value, err = strconv.ParseFloat(txt, 64)
|
value, err = strconv.ParseFloat(txt, 64)
|
||||||
|
} else if sym == SymFraction {
|
||||||
|
value, err = makeGeneratingFraction(txt)
|
||||||
} else {
|
} else {
|
||||||
value, err = strconv.ParseInt(txt, numBase, 64)
|
value, err = strconv.ParseInt(txt, numBase, 64)
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
tk = self.makeValueToken(sym, txt, value)
|
tk = scanner.makeValueToken(sym, txt, value)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeErrorToken(err)
|
tk = scanner.makeErrorToken(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) fetchIdentifier(firstCh byte) (tk *Token) {
|
func (scanner *scanner) fetchIdentifier(firstCh byte) (tk *Token) {
|
||||||
var err error
|
var err error
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
for ch := firstCh; err == nil && (ch == '_' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')); ch, err = self.readChar() {
|
for ch := firstCh; err == nil && (ch == '_' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')); ch, err = scanner.readChar() {
|
||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
tk = self.makeErrorToken(err)
|
tk = scanner.makeErrorToken(err)
|
||||||
} else if err = self.sync(err); err != nil && err != io.EOF {
|
} else if err = scanner.sync(err); err != nil && err != io.EOF {
|
||||||
tk = self.makeErrorToken(err)
|
tk = scanner.makeErrorToken(err)
|
||||||
} else {
|
} else {
|
||||||
txt := sb.String()
|
txt := sb.String()
|
||||||
uptxt := strings.ToUpper(txt)
|
uptxt := strings.ToUpper(txt)
|
||||||
if sym, ok := keywords[uptxt]; ok {
|
if sym, ok := keywords[uptxt]; ok {
|
||||||
tk = self.makeKeywordToken(sym, uptxt)
|
tk = scanner.makeKeywordToken(sym, uptxt)
|
||||||
} else if uptxt == `TRUE` {
|
} else if uptxt == `TRUE` {
|
||||||
tk = self.makeValueToken(SymBool, txt, true)
|
tk = scanner.makeValueToken(SymBool, txt, true)
|
||||||
} else if uptxt == `FALSE` {
|
} else if uptxt == `FALSE` {
|
||||||
tk = self.makeValueToken(SymBool, txt, false)
|
tk = scanner.makeValueToken(SymBool, txt, false)
|
||||||
} else if ch, _ := self.peek(); ch == '(' {
|
} else if ch, _ := scanner.peek(); ch == '(' {
|
||||||
self.readChar()
|
scanner.readChar()
|
||||||
tk = self.makeValueToken(SymFuncCall, txt+"(", txt)
|
tk = scanner.makeValueToken(SymFuncCall, txt+"(", txt)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeValueToken(SymIdentifier, txt, txt)
|
tk = scanner.makeValueToken(SymIdentifier, txt, txt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,29 +502,29 @@ func (self *scanner) fetchIdentifier(firstCh byte) (tk *Token) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) fetchBlockComment() *Token {
|
func (scanner *scanner) fetchBlockComment() *Token {
|
||||||
return self.fetchUntil(SymComment, false, '*', '/')
|
return scanner.fetchUntil(SymComment, false, '*', '/')
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) fetchOnLineComment() *Token {
|
func (scanner *scanner) fetchOnLineComment() *Token {
|
||||||
return self.fetchUntil(SymComment, true, '\n')
|
return scanner.fetchUntil(SymComment, true, '\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk *Token) {
|
func (scanner *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk *Token) {
|
||||||
var err error
|
var err error
|
||||||
var ch byte
|
var ch byte
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
var value string
|
var value string
|
||||||
ring := NewByteSlider(len(endings))
|
ring := NewByteSlider(len(endings))
|
||||||
endReached := false
|
endReached := false
|
||||||
for ch, err = self.readChar(); err == nil && !endReached; {
|
for ch, err = scanner.readChar(); err == nil && !endReached; {
|
||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
ring.PushEnd(ch)
|
ring.PushEnd(ch)
|
||||||
if ring.Equal(endings) {
|
if ring.Equal(endings) {
|
||||||
value = sb.String()[0 : sb.Len()-len(endings)]
|
value = sb.String()[0 : sb.Len()-len(endings)]
|
||||||
endReached = true
|
endReached = true
|
||||||
} else {
|
} else {
|
||||||
ch, err = self.readChar()
|
ch, err = scanner.readChar()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !endReached && allowEos {
|
if !endReached && allowEos {
|
||||||
@@ -499,18 +533,18 @@ func (self *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk
|
|||||||
}
|
}
|
||||||
|
|
||||||
if endReached {
|
if endReached {
|
||||||
tk = self.makeValueToken(sym, "", value)
|
tk = scanner.makeValueToken(sym, "", value)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeErrorToken(err)
|
tk = scanner.makeErrorToken(err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) fetchString() (tk *Token) {
|
func (scanner *scanner) fetchString(termCh byte) (tk *Token) {
|
||||||
var err error
|
var err error
|
||||||
var ch, prev byte
|
var ch, prev byte
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
for ch, err = self.readChar(); err == nil; ch, err = self.readChar() {
|
for ch, err = scanner.readChar(); err == nil; ch, err = scanner.readChar() {
|
||||||
if prev == '\\' {
|
if prev == '\\' {
|
||||||
switch ch {
|
switch ch {
|
||||||
case '"':
|
case '"':
|
||||||
@@ -527,7 +561,7 @@ func (self *scanner) fetchString() (tk *Token) {
|
|||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
}
|
}
|
||||||
prev = 0
|
prev = 0
|
||||||
} else if ch == '"' {
|
} else if ch == termCh {
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
prev = ch
|
prev = ch
|
||||||
@@ -538,65 +572,65 @@ func (self *scanner) fetchString() (tk *Token) {
|
|||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
tk = self.makeErrorToken(errors.New("missing string termination \""))
|
tk = scanner.makeErrorToken(errors.New("missing string termination \""))
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeErrorToken(err)
|
tk = scanner.makeErrorToken(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
txt := sb.String()
|
txt := sb.String()
|
||||||
tk = self.makeValueToken(SymString, `"`+txt+`"`, txt)
|
tk = scanner.makeValueToken(SymString, `"`+txt+`"`, txt)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) peek() (next byte, err error) {
|
func (scanner *scanner) peek() (next byte, err error) {
|
||||||
var one []byte
|
var one []byte
|
||||||
if one, err = self.stream.Peek(1); err == nil {
|
if one, err = scanner.stream.Peek(1); err == nil {
|
||||||
next = one[0]
|
next = one[0]
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) skipBlanks() (err error) {
|
func (scanner *scanner) skipBlanks() (err error) {
|
||||||
var one []byte
|
var one []byte
|
||||||
for one, err = self.stream.Peek(1); err == nil && one[0] <= 32; one, err = self.stream.Peek(1) {
|
for one, err = scanner.stream.Peek(1); err == nil && one[0] <= 32; one, err = scanner.stream.Peek(1) {
|
||||||
self.readChar()
|
scanner.readChar()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) translate(sym Symbol) Symbol {
|
func (scanner *scanner) translate(sym Symbol) Symbol {
|
||||||
if self.translations != nil {
|
if scanner.translations != nil {
|
||||||
if translatedSym, ok := self.translations[sym]; ok {
|
if translatedSym, ok := scanner.translations[sym]; ok {
|
||||||
return translatedSym
|
return translatedSym
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sym
|
return sym
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) moveOn(sym Symbol, chars ...byte) (tk *Token) {
|
func (scanner *scanner) moveOn(sym Symbol, chars ...byte) (tk *Token) {
|
||||||
tk = NewToken(self.row, self.column, self.translate(sym), string(chars))
|
tk = NewToken(scanner.row, scanner.column, scanner.translate(sym), string(chars))
|
||||||
for i := 1; i < len(chars); i++ {
|
for i := 1; i < len(chars); i++ {
|
||||||
self.readChar()
|
scanner.readChar()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) makeToken(sym Symbol, chars ...byte) (tk *Token) {
|
func (scanner *scanner) makeToken(sym Symbol, chars ...byte) (tk *Token) {
|
||||||
tk = NewToken(self.row, self.column, self.translate(sym), string(chars))
|
tk = NewToken(scanner.row, scanner.column, scanner.translate(sym), string(chars))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) makeKeywordToken(sym Symbol, upperCaseKeyword string) (tk *Token) {
|
func (scanner *scanner) makeKeywordToken(sym Symbol, upperCaseKeyword string) (tk *Token) {
|
||||||
tk = NewToken(self.row, self.column, self.translate(sym), upperCaseKeyword)
|
tk = NewToken(scanner.row, scanner.column, scanner.translate(sym), upperCaseKeyword)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) makeValueToken(sym Symbol, source string, value any) (tk *Token) {
|
func (scanner *scanner) makeValueToken(sym Symbol, source string, value any) (tk *Token) {
|
||||||
tk = NewValueToken(self.row, self.column, self.translate(sym), source, value)
|
tk = NewValueToken(scanner.row, scanner.column, scanner.translate(sym), source, value)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) makeErrorToken(err error) *Token {
|
func (scanner *scanner) makeErrorToken(err error) *Token {
|
||||||
return NewErrorToken(self.row, self.column, err)
|
return NewErrorToken(scanner.row, scanner.column, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
+190
@@ -0,0 +1,190 @@
|
|||||||
|
// 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 {
|
||||||
|
clone := &SimpleStore{
|
||||||
|
varStore: CloneFilteredMap(ctx.varStore, filterRefName),
|
||||||
|
funcStore: CloneFilteredMap(ctx.funcStore, filterRefName),
|
||||||
|
}
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (ctx *SimpleStore) Merge(src ExprContext) {
|
||||||
|
// for _, name := range src.EnumVars(filterRefName) {
|
||||||
|
// ctx.varStore[name], _ = src.GetVar(name)
|
||||||
|
// }
|
||||||
|
// for _, name := range src.EnumFuncs(filterRefName) {
|
||||||
|
// ctx.funcStore[name], _ = src.GetFuncInfo(name)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) ToString(opt FmtOpt) string {
|
||||||
|
dict := ctx.ToDict()
|
||||||
|
return dict.ToString(opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) varsToDict(dict *DictType) *DictType {
|
||||||
|
names := ctx.EnumVars(nil)
|
||||||
|
slices.Sort(names)
|
||||||
|
for _, name := range ctx.EnumVars(nil) {
|
||||||
|
value, _ := ctx.GetVar(name)
|
||||||
|
if f, ok := value.(Formatter); ok {
|
||||||
|
(*dict)[name] = f.ToString(0)
|
||||||
|
} else if _, ok = value.(Functor); ok {
|
||||||
|
(*dict)[name] = "func(){}"
|
||||||
|
} else {
|
||||||
|
(*dict)[name] = fmt.Sprintf("%v", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dict
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) funcsToDict(dict *DictType) *DictType {
|
||||||
|
names := ctx.EnumFuncs(func(name string) bool { return true })
|
||||||
|
slices.Sort(names)
|
||||||
|
for _, name := range names {
|
||||||
|
value, _ := ctx.GetFuncInfo(name)
|
||||||
|
if formatter, ok := value.(Formatter); ok {
|
||||||
|
(*dict)[name] = formatter.ToString(0)
|
||||||
|
} else {
|
||||||
|
(*dict)[name] = fmt.Sprintf("%v", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dict
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) ToDict() (dict *DictType) {
|
||||||
|
dict = MakeDict()
|
||||||
|
(*dict)["variables"] = ctx.varsToDict(MakeDict())
|
||||||
|
(*dict)["functions"] = ctx.funcsToDict(MakeDict())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) GetVar(varName string) (v any, exists bool) {
|
||||||
|
v, exists = ctx.varStore[varName]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) GetLast() (v any) {
|
||||||
|
v = ctx.varStore["last"]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) UnsafeSetVar(varName string, value any) {
|
||||||
|
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
|
||||||
|
ctx.varStore[varName] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
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) VarCount() int {
|
||||||
|
return len(ctx.varStore)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) DeleteVar(varName string) {
|
||||||
|
delete(ctx.varStore, varName)
|
||||||
|
}
|
||||||
|
|
||||||
|
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) FuncCount() int {
|
||||||
|
return len(ctx.funcStore)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *SimpleStore) DeleteFunc(funcName string) {
|
||||||
|
delete(ctx.funcStore, funcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
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) setVar(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()
|
|
||||||
}
|
|
||||||
@@ -57,22 +57,25 @@ const (
|
|||||||
SymTilde // 46: '~'
|
SymTilde // 46: '~'
|
||||||
SymDoubleQuestion // 47: '??'
|
SymDoubleQuestion // 47: '??'
|
||||||
SymQuestionEqual // 48: '?='
|
SymQuestionEqual // 48: '?='
|
||||||
SymDoubleAt // 49: '@@'
|
SymQuestionExclam // 49: '?!'
|
||||||
SymDoubleColon // 50: '::'
|
SymDoubleAt // 50: '@@'
|
||||||
SymInsert // 51: '>>'
|
SymDoubleColon // 51: '::'
|
||||||
SymAppend // 52: '<<'
|
SymInsert // 52: '>>'
|
||||||
SymCaret // 53: '^'
|
SymAppend // 53: '<<'
|
||||||
SymDollarRound // 54: '$('
|
SymCaret // 54: '^'
|
||||||
SymOpenClosedRound // 55: '()'
|
SymDollarRound // 55: '$('
|
||||||
SymDoubleDollar // 56: '$$'
|
SymOpenClosedRound // 56: '()'
|
||||||
SymDoubleDot // 57: '..'
|
SymDoubleDollar // 57: '$$'
|
||||||
SymTripleDot // 58: '...'
|
SymDoubleDot // 58: '..'
|
||||||
|
SymTripleDot // 59: '...'
|
||||||
SymChangeSign
|
SymChangeSign
|
||||||
SymUnchangeSign
|
SymUnchangeSign
|
||||||
SymIdentifier
|
SymIdentifier
|
||||||
SymBool
|
SymBool
|
||||||
SymInteger
|
SymInteger
|
||||||
|
SymVariable
|
||||||
SymFloat
|
SymFloat
|
||||||
|
SymFraction
|
||||||
SymString
|
SymString
|
||||||
SymIterator
|
SymIterator
|
||||||
SymOr
|
SymOr
|
||||||
@@ -83,6 +86,7 @@ const (
|
|||||||
SymFuncDef
|
SymFuncDef
|
||||||
SymList
|
SymList
|
||||||
SymDict
|
SymDict
|
||||||
|
SymIndex
|
||||||
SymExpression
|
SymExpression
|
||||||
SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
|
SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
|
||||||
SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
|
SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
|
||||||
@@ -98,8 +102,11 @@ const (
|
|||||||
SymKwBut
|
SymKwBut
|
||||||
SymKwFunc
|
SymKwFunc
|
||||||
SymKwBuiltin
|
SymKwBuiltin
|
||||||
|
SymKwPlugin
|
||||||
|
SymKwIn
|
||||||
SymKwInclude
|
SymKwInclude
|
||||||
SymKwNil
|
SymKwNil
|
||||||
|
SymKwUnset
|
||||||
)
|
)
|
||||||
|
|
||||||
var keywords map[string]Symbol
|
var keywords map[string]Symbol
|
||||||
@@ -109,11 +116,14 @@ func init() {
|
|||||||
keywords = map[string]Symbol{
|
keywords = map[string]Symbol{
|
||||||
"AND": SymKwAnd,
|
"AND": SymKwAnd,
|
||||||
"BUILTIN": SymKwBuiltin,
|
"BUILTIN": SymKwBuiltin,
|
||||||
|
"PLUGIN": SymKwPlugin,
|
||||||
"BUT": SymKwBut,
|
"BUT": SymKwBut,
|
||||||
"FUNC": SymKwFunc,
|
"FUNC": SymKwFunc,
|
||||||
|
"IN": SymKwIn,
|
||||||
"INCLUDE": SymKwInclude,
|
"INCLUDE": SymKwInclude,
|
||||||
"NOT": SymKwNot,
|
"NOT": SymKwNot,
|
||||||
"OR": SymKwOr,
|
"OR": SymKwOr,
|
||||||
"NIL": SymKwNil,
|
"NIL": SymKwNil,
|
||||||
|
"UNSET": SymKwUnset,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
// ast_test.go
|
// t_ast_test.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_bool_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBool(t *testing.T) {
|
||||||
|
section := "Bool"
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {`true`, true, nil},
|
||||||
|
/* 2 */ {`false`, false, nil},
|
||||||
|
/* 3 */ {`not false`, true, nil},
|
||||||
|
/* 4 */ {`not 1`, false, nil},
|
||||||
|
/* 5 */ {`not "true"`, false, nil},
|
||||||
|
/* 6 */ {`not "false"`, false, nil},
|
||||||
|
/* 7 */ {`not ""`, true, nil},
|
||||||
|
/* 8 */ {`not []`, nil, errors.New(`[1:4] prefix/postfix operator "NOT" do not support operand '[]' [list]`)},
|
||||||
|
/* 9 */ {`true and false`, false, nil},
|
||||||
|
/* 10 */ {`true and []`, nil, errors.New(`[1:9] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "AND"`)},
|
||||||
|
/* 11 */ {`[] and false`, nil, errors.New(`got list as left operand type of 'AND' operator, it must be bool`)},
|
||||||
|
/* 12 */ {`true or false`, true, nil},
|
||||||
|
/* 13 */ {`true or []`, true, nil},
|
||||||
|
/* 14 */ {`[] or false`, nil, errors.New(`got list as left operand type of 'OR' operator, it must be bool`)},
|
||||||
|
/* 13 */ //{`true or []`, nil, errors.New(`[1:8] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "OR"`)},
|
||||||
|
}
|
||||||
|
|
||||||
|
// t.Setenv("EXPR_PATH", ".")
|
||||||
|
|
||||||
|
// runTestSuiteSpec(t, section, inputs, 1)
|
||||||
|
runTestSuite(t, section, inputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolNoShortcut(t *testing.T) {
|
||||||
|
section := "Bool-NoShortcut"
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {`true`, true, nil},
|
||||||
|
/* 2 */ {`false`, false, nil},
|
||||||
|
/* 3 */ {`not false`, true, nil},
|
||||||
|
/* 4 */ {`not 1`, false, nil},
|
||||||
|
/* 5 */ {`not "true"`, false, nil},
|
||||||
|
/* 6 */ {`not "false"`, false, nil},
|
||||||
|
/* 7 */ {`not ""`, true, nil},
|
||||||
|
/* 8 */ {`not []`, nil, `[1:4] prefix/postfix operator "NOT" do not support operand '[]' [list]`},
|
||||||
|
/* 9 */ {`true and false`, false, nil},
|
||||||
|
/* 10 */ {`true and []`, nil, `[1:9] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "AND"`},
|
||||||
|
/* 11 */ {`[] and false`, nil, `[1:7] left operand '[]' [list] and right operand 'false' [bool] are not compatible with operator "AND"`},
|
||||||
|
/* 12 */ {`true or false`, true, nil},
|
||||||
|
/* 13 */ {`true or []`, nil, `[1:8] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "OR"`},
|
||||||
|
/* 14 */ {`[] or false`, nil, `[1:6] left operand '[]' [list] and right operand 'false' [bool] are not compatible with operator "OR"`},
|
||||||
|
}
|
||||||
|
|
||||||
|
// t.Setenv("EXPR_PATH", ".")
|
||||||
|
|
||||||
|
ctx := NewSimpleStore()
|
||||||
|
current := SetCtrl(ctx, ControlBoolShortcut, false)
|
||||||
|
|
||||||
|
// runCtxTestSuiteSpec(t, ctx, section, inputs, 1)
|
||||||
|
runCtxTestSuite(t, ctx, section, inputs)
|
||||||
|
|
||||||
|
SetCtrl(ctx, ControlBoolShortcut, current)
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_builtin-base_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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, `strconv.Atoi: parsing "1.5": invalid syntax`},
|
||||||
|
/* 10 */ {`int("432", 4)`, nil, `int(): too much params -- expected 1, got 2`},
|
||||||
|
/* 11 */ {`int(nil)`, nil, `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, `[1:11] missing string termination "`},
|
||||||
|
/* 31 */ {`dec()`, nil, `dec(): too few params -- expected 1, got 0`},
|
||||||
|
/* 32 */ {`dec(1,2,3)`, nil, `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, `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, `dec(): can't convert list to float`},
|
||||||
|
// /* 45 */ {`string([1])`, nil, `string(): can't convert list to string`},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Setenv("EXPR_PATH", ".")
|
||||||
|
|
||||||
|
runTestSuite(t, section, inputs)
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_builtin-fmt.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
// "errors"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFuncFmt(t *testing.T) {
|
||||||
|
section := "Builtin-Fmt"
|
||||||
|
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {`builtin "fmt"; print("ciao")`, int64(4), nil},
|
||||||
|
/* 2 */ {`builtin "fmt"; println(" ciao")`, int64(6), nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
//t.Setenv("EXPR_PATH", ".")
|
||||||
|
|
||||||
|
// runTestSuiteSpec(t, section, inputs, 1)
|
||||||
|
runTestSuite(t, section, inputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFmt(t *testing.T) {
|
||||||
|
section := "Builtin-Fmt"
|
||||||
|
|
||||||
|
text := "ciao mondo"
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {fmt.Sprintf(`println("%s")`, text), int64(11), nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
// t.Setenv("EXPR_PATH", ".")
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
ctx := NewSimpleStore()
|
||||||
|
currentStdout := SetCtrl(ctx, ControlStdout, &b)
|
||||||
|
|
||||||
|
runCtxTestSuite(t, ctx, section, inputs)
|
||||||
|
|
||||||
|
SetCtrl(ctx, ControlStdout, currentStdout)
|
||||||
|
if b.String() != text+"\n" {
|
||||||
|
t.Errorf("println(): Got: %q, Want: %q", b.String(), text+"\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_builtin-import_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFuncImport(t *testing.T) {
|
||||||
|
section := "Builtin-Import"
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {`builtin "import"; import("./test-resources/test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
||||||
|
/* 2 */ {`builtin "import"; import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
||||||
|
/* 3 */ {`builtin "import"; importAll("./test-resources/test-funcs.expr"); six()`, int64(6), nil},
|
||||||
|
/* 4 */ {`builtin "import"; import("./test-resources/sample-export-all.expr"); six()`, int64(6), nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Setenv("EXPR_PATH", "test-resources")
|
||||||
|
|
||||||
|
// runTestSuiteSpec(t, section, inputs, 1)
|
||||||
|
runTestSuite(t, section, inputs)
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_builtin-iterator.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFuncRun(t *testing.T) {
|
||||||
|
section := "Builtin-Iterator"
|
||||||
|
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {`builtin "iterator"; it=$(1,2,3); run(it)`, nil, nil},
|
||||||
|
/* 2 */ {`builtin "iterator"; run($(1,2,3), func(index,item){item+10})`, nil, nil},
|
||||||
|
/* 3 */ {`builtin "iterator"; run($(1,2,3), func(index,item){it_status=it_status+item; true}, {"it_status":0})`, int64(6), nil},
|
||||||
|
/* 4 */ {`builtin ["iterator", "fmt"]; run($(1,2,3), func(index,item){println(item+10)})`, nil, nil},
|
||||||
|
/* 5 */ {`builtin "iterator"; run(nil)`, nil, `paramter "iterator" must be an iterator, passed <nil> [nil]`},
|
||||||
|
/* 6 */ {`builtin "iterator"; run($(1,2,3), nil)`, nil, `paramter "operator" must be a function, passed <nil> [nil]`},
|
||||||
|
/* 7 */ {`builtin "iterator"; run($(1,2,3), func(){1}, "prrr")`, nil, `paramter "vars" must be a dictionary, passed prrr [string]`},
|
||||||
|
}
|
||||||
|
|
||||||
|
//t.Setenv("EXPR_PATH", ".")
|
||||||
|
|
||||||
|
//runTestSuiteSpec(t, section, inputs, 3)
|
||||||
|
runTestSuite(t, section, inputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// func TestFmt(t *testing.T) {
|
||||||
|
// section := "Builtin-Fmt"
|
||||||
|
|
||||||
|
// text := "ciao mondo"
|
||||||
|
// inputs := []inputType{
|
||||||
|
// /* 1 */ {fmt.Sprintf(`println("%s")`, text), int64(11), nil},
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // t.Setenv("EXPR_PATH", ".")
|
||||||
|
|
||||||
|
// var b bytes.Buffer
|
||||||
|
// ctx := NewSimpleStore()
|
||||||
|
// currentStdout := SetCtrl(ctx, ControlStdout, &b)
|
||||||
|
|
||||||
|
// runCtxTestSuite(t, ctx, section, inputs)
|
||||||
|
|
||||||
|
// SetCtrl(ctx, ControlStdout, currentStdout)
|
||||||
|
// if b.String() != text+"\n" {
|
||||||
|
// t.Errorf("println(): Got: %q, Want: %q", b.String(), text+"\n")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_builtin-math-arith_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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, `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", ".")
|
||||||
|
|
||||||
|
// runTestSuiteSpec(t, section, inputs, 1)
|
||||||
|
runTestSuite(t, section, inputs)
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_builtin-os-file_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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, `open /etc/hostsX: no such file or directory`},
|
||||||
|
/* 4 */ {`builtin "os.file"; handle=fileCreate("/tmp/dummy"); fileClose(handle)`, true, nil},
|
||||||
|
/* 5 */ {`builtin "os.file"; handle=fileAppend("/tmp/dummy"); fileWriteText(handle, "bye-bye"); fileClose(handle)`, true, nil},
|
||||||
|
/* 6 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); word=fileReadText(handle, "-"); fileClose(handle);word`, "bye", nil},
|
||||||
|
/* 7 */ {`builtin "os.file"; word=fileReadText(nil, "-")`, nil, `fileReadText(): invalid file handle`},
|
||||||
|
/* 8 */ {`builtin "os.file"; fileWriteText(nil, "bye")`, nil, `fileWriteText(): invalid file handle`},
|
||||||
|
/* 9 */ {`builtin "os.file"; handle=fileOpen()`, nil, `fileOpen(): too few params -- expected 1, got 0`},
|
||||||
|
/* 10 */ {`builtin "os.file"; handle=fileOpen(123)`, nil, `fileOpen(): missing or invalid file path`},
|
||||||
|
/* 11 */ {`builtin "os.file"; handle=fileCreate(123)`, nil, `fileCreate(): missing or invalid file path`},
|
||||||
|
/* 12 */ {`builtin "os.file"; handle=fileAppend(123)`, nil, `fileAppend(): missing or invalid file path`},
|
||||||
|
/* 13 */ {`builtin "os.file"; handle=fileClose(123)`, nil, `fileClose(): invalid file handle`},
|
||||||
|
/* 14 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); c=fileReadTextAll(handle); fileClose(handle); c`, "bye-bye", nil},
|
||||||
|
/* 15 */ {`builtin "os.file"; c=fileReadTextAll(123)`, nil, `fileReadTextAll(): invalid file handle 123 [int64]`},
|
||||||
|
}
|
||||||
|
|
||||||
|
// t.Setenv("EXPR_PATH", ".")
|
||||||
|
|
||||||
|
// runTestSuiteSpec(t, section, inputs, 1)
|
||||||
|
runTestSuite(t, section, inputs)
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_builtin-string_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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, `strJoin(): the "separator" parameter must be a string, got a integer (1)`},
|
||||||
|
/* 5 */ {`builtin "string"; ls= ["one", 2, "three"]; strJoin("-", ls)`, nil, `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, `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, `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, `strJoin(): expected string, got integer (1)`},
|
||||||
|
/* 19 */ {`builtin "string"; strJoin()`, nil, `strJoin(): too few params -- expected 1 or more, got 0`},
|
||||||
|
|
||||||
|
/* 69 */ /*{`builtin "string"; $$global`, `vars: {
|
||||||
|
}
|
||||||
|
funcs: {
|
||||||
|
add(any=0 ...) -> number,
|
||||||
|
dec(any) -> decimal,
|
||||||
|
endsWithStr(source, suffix) -> boolean,
|
||||||
|
fract(any, denominator=1) -> fraction,
|
||||||
|
import( ...) -> any,
|
||||||
|
importAll( ...) -> any,
|
||||||
|
int(any) -> integer,
|
||||||
|
isBool(any) -> boolean,
|
||||||
|
isDec(any) -> boolean,
|
||||||
|
isDict(any) -> boolean,
|
||||||
|
isFloat(any) -> boolean,
|
||||||
|
isFract(any) -> boolean,
|
||||||
|
isInt(any) -> boolean,
|
||||||
|
isList(any) -> boolean,
|
||||||
|
isNil(any) -> boolean,
|
||||||
|
isString(any) -> boolean,
|
||||||
|
joinStr(separator, item="" ...) -> string,
|
||||||
|
mul(any=1 ...) -> number,
|
||||||
|
splitStr(source, separator="", count=-1) -> list of string,
|
||||||
|
startsWithStr(source, prefix) -> boolean,
|
||||||
|
subStr(source, start=0, count=-1) -> string,
|
||||||
|
trimStr(source) -> string
|
||||||
|
}
|
||||||
|
`, nil},*/
|
||||||
|
}
|
||||||
|
|
||||||
|
//t.Setenv("EXPR_PATH", ".")
|
||||||
|
|
||||||
|
// runTestSuiteSpec(t, section, inputs, 19)
|
||||||
|
runTestSuite(t, section, inputs)
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// t_common_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type inputType struct {
|
||||||
|
source string
|
||||||
|
wantResult any
|
||||||
|
wantErr any
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCtxTestSuiteSpec(t *testing.T, ctx ExprContext, section string, inputs []inputType, spec ...int) {
|
||||||
|
succeeded := 0
|
||||||
|
failed := 0
|
||||||
|
for _, count := range spec {
|
||||||
|
good := doTest(t, ctx, section, &inputs[count-1], count)
|
||||||
|
|
||||||
|
if good {
|
||||||
|
succeeded++
|
||||||
|
} else {
|
||||||
|
failed++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(spec), succeeded, failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTestSuiteSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
|
||||||
|
runCtxTestSuiteSpec(t, nil, section, inputs, spec...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCtxTestSuite(t *testing.T, ctx ExprContext, section string, inputs []inputType) {
|
||||||
|
|
||||||
|
succeeded := 0
|
||||||
|
failed := 0
|
||||||
|
|
||||||
|
for i, input := range inputs {
|
||||||
|
good := doTest(t, ctx, section, &input, i+1)
|
||||||
|
if good {
|
||||||
|
succeeded++
|
||||||
|
} else {
|
||||||
|
failed++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||||
|
}
|
||||||
|
func runTestSuite(t *testing.T, section string, inputs []inputType) {
|
||||||
|
runCtxTestSuite(t, nil, section, inputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWantedError(input *inputType) error {
|
||||||
|
var wantErr error
|
||||||
|
var ok bool
|
||||||
|
if wantErr, ok = input.wantErr.(error); !ok {
|
||||||
|
if msg, ok := input.wantErr.(string); ok {
|
||||||
|
wantErr = errors.New(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wantErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTest(t *testing.T, ctx ExprContext, section string, input *inputType, count int) (good bool) {
|
||||||
|
var expr Expr
|
||||||
|
var gotResult any
|
||||||
|
var gotErr error
|
||||||
|
|
||||||
|
wantErr := getWantedError(input)
|
||||||
|
|
||||||
|
parser := NewParser()
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = NewSimpleStore()
|
||||||
|
}
|
||||||
|
|
||||||
|
logTest(t, count, section, input.source, input.wantResult, 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 != wantErr {
|
||||||
|
if wantErr == nil || gotErr == nil || (gotErr.Error() != wantErr.Error()) {
|
||||||
|
t.Errorf("%d: %q -> got-err = <%v>, expected-err = <%v>", count, input.source, gotErr, wantErr)
|
||||||
|
good = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
|
||||||
|
if wantErr == nil {
|
||||||
|
t.Logf("[+]%s nr %3d -- %q --> %v", section, n, source, wantResult)
|
||||||
|
} else {
|
||||||
|
t.Logf("[-]%s nr %3d -- %q --> %v", section, n, source, wantErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
// All rights reserved.
|
// All rights reserved.
|
||||||
|
|
||||||
// list_test.go
|
// t_dict_test.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestListParser(t *testing.T) {
|
func TestDictParser(t *testing.T) {
|
||||||
section := "List"
|
section := "Dict"
|
||||||
|
|
||||||
type inputType struct {
|
type inputType struct {
|
||||||
source string
|
source string
|
||||||
@@ -20,22 +21,17 @@ func TestListParser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inputs := []inputType{
|
inputs := []inputType{
|
||||||
/* 1 */ {`[]`, []any{}, nil},
|
/* 1 */ {`{}`, map[any]any{}, nil},
|
||||||
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
/* 2 */ {`{123}`, nil, errors.New(`[1:6] expected ":", got "}"`)},
|
||||||
/* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil},
|
/* 3 */ {`{1:"one",2:"two",3:"three"}`, map[int64]any{int64(1): "one", int64(2): "two", int64(3): "three"}, nil},
|
||||||
/* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil},
|
/* 4 */ {`{1:"one",2:"two",3:"three"}[3]`, "three", nil},
|
||||||
/* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
/* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil},
|
||||||
/* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil},
|
/* 6 */ {`{1:"one"} + {2:"two"}`, map[any]any{1: "one", 2: "two"}, nil},
|
||||||
/* 7 */ {`add([1,4,3,2])`, int64(10), nil},
|
/* 7 */ {`2 in {1:"one", 2:"two"}`, true, nil},
|
||||||
/* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
|
/* 8 */ {`D={"a":1, "b":2}; D["a"]=9; D`, map[any]any{"a": 9, "b": 2}, nil},
|
||||||
/* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
|
/* 9 */ {`D={"a":1, "b":2}; D["z"]=9; D`, map[any]any{"z": 9, "a": 1, "b": 2}, nil},
|
||||||
/* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 (2 in 1) has wrong type string, number expected`)},
|
/* 10 */ {`D={"a":1, "b":2}; D[nil]=9`, nil, errors.New(`[1:21] index/key is nil`)},
|
||||||
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
|
/* 11 */ {`D={"a":1, "b":2}; D["a"]`, int64(1), 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},
|
|
||||||
|
|
||||||
// /* 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
|
succeeded := 0
|
||||||
@@ -50,13 +46,13 @@ func TestListParser(t *testing.T) {
|
|||||||
var gotResult any
|
var gotResult any
|
||||||
var gotErr error
|
var gotErr error
|
||||||
|
|
||||||
ctx := NewSimpleFuncStore()
|
ctx := NewSimpleStore()
|
||||||
ctx.SetVar("var1", int64(123))
|
ctx.SetVar("var1", int64(123))
|
||||||
ctx.SetVar("var2", "abc")
|
ctx.SetVar("var2", "abc")
|
||||||
ImportMathFuncs(ctx)
|
ImportMathFuncs(ctx)
|
||||||
parser := NewParser(ctx)
|
parser := NewParser()
|
||||||
|
|
||||||
logTest(t, i+1, "List", input.source, input.wantResult, input.wantErr)
|
logTest(t, i+1, "Dict", input.source, input.wantResult, input.wantErr)
|
||||||
|
|
||||||
r := strings.NewReader(input.source)
|
r := strings.NewReader(input.source)
|
||||||
scanner := NewScanner(r, DefaultTranslations())
|
scanner := NewScanner(r, DefaultTranslations())
|
||||||
@@ -110,3 +106,49 @@ func TestListParser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDictToStringMultiLine(t *testing.T) {
|
||||||
|
var good bool
|
||||||
|
section := "dict-ToString-ML"
|
||||||
|
want := `{
|
||||||
|
"first": 1
|
||||||
|
}`
|
||||||
|
args := map[any]*term{
|
||||||
|
"first": newLiteralTerm(NewValueToken(0, 0, SymInteger, "1", 1)),
|
||||||
|
}
|
||||||
|
dict := newDict(args)
|
||||||
|
got := dict.ToString(MultiLine)
|
||||||
|
// fmt.Printf("got=%q\n", got)
|
||||||
|
|
||||||
|
if good = got == want; !good {
|
||||||
|
t.Errorf("ToString(MultiLine): got = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if good {
|
||||||
|
t.Logf("%s -- succeeded", section)
|
||||||
|
} else {
|
||||||
|
t.Logf("%s -- failed", section)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDictToString(t *testing.T) {
|
||||||
|
var good bool
|
||||||
|
section := "dict-ToString-SL"
|
||||||
|
want := `{"first": 1}`
|
||||||
|
args := map[any]*term{
|
||||||
|
"first": newLiteralTerm(NewValueToken(0, 0, SymInteger, "1", 1)),
|
||||||
|
}
|
||||||
|
dict := newDict(args)
|
||||||
|
got := dict.ToString(0)
|
||||||
|
// fmt.Printf("got=%q\n", got)
|
||||||
|
|
||||||
|
if good = got == want; !good {
|
||||||
|
t.Errorf("ToString(0): got = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if good {
|
||||||
|
t.Logf("%s -- succeeded", section)
|
||||||
|
} else {
|
||||||
|
t.Logf("%s -- failed", section)
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user