Compare commits
266 Commits
Author | SHA1 | Date | |
---|---|---|---|
5ecf81412e | |||
ff4db34f7b | |||
0f848071c2 | |||
6b3351b324 | |||
760c1ee6da | |||
5ab6876ea1 | |||
6268abda8c | |||
d25bd325b7 | |||
01c04feea5 | |||
6fc689c46c | |||
eccb0c4dc9 | |||
e43823740f | |||
526982a564 | |||
252514943e | |||
32686fac62 | |||
52a627c747 | |||
d91e7eb979 | |||
cca3b76baa | |||
24e31997fc | |||
646710e180 | |||
b38327b841 | |||
fd912b2eb1 | |||
0e55f83d56 | |||
4725145d1c | |||
edf8818f51 | |||
6211be8a8f | |||
f50ddf48db | |||
76e01f12d2 | |||
406bced450 | |||
409dc86a92 | |||
4184221428 | |||
8cf8b36a26 | |||
de87050188 | |||
a1ec0cc611 | |||
8e5550bfa7 | |||
6ee21e10af | |||
5c44532790 | |||
cb66c1ab19 | |||
a28d24ba68 | |||
523349a204 | |||
b185f1df3a | |||
5da5a61a42 | |||
6e9205abc4 | |||
f61004fb5d | |||
321030c8d3 | |||
98fc89e84f | |||
778d00677d | |||
ba3dbb7f02 | |||
7285109115 | |||
4755774edd | |||
d215d837f6 | |||
ad3c1e5a60 | |||
d6bf5ee500 | |||
4b176eb868 | |||
dceb31f542 | |||
075b0b5691 | |||
3c51b8d2ee | |||
a46753f453 | |||
9070b5c9cc | |||
ab06702e5e | |||
ffe1fa3aac | |||
1a1a475dd8 | |||
463e3634ba | |||
5f8ca47ef0 | |||
837b887490 | |||
c76e1d3c8e | |||
315f5b22d3 | |||
dfae593e86 | |||
d572f3a129 | |||
c461fd138e | |||
6ecbe2feb1 | |||
80d3c6ec7d | |||
e09806c716 | |||
1a772597cb | |||
33b3e1fc29 | |||
4e3f5cfbc6 | |||
e35d4e3f70 | |||
b4529499d6 | |||
7745dc24e2 | |||
be25385d02 | |||
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 |
89
ast.go
89
ast.go
@ -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)
|
|
||||||
|
func (expr *ast) ToForest() {
|
||||||
|
if expr.root != nil {
|
||||||
|
if expr.forest == nil {
|
||||||
|
expr.forest = make([]*term, 0)
|
||||||
}
|
}
|
||||||
self.forest = append(self.forest, self.root)
|
expr.forest = append(expr.forest, expr.root)
|
||||||
self.root = nil
|
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) addToken(tk *Token) (t *term, err error) {
|
||||||
if t = newTerm(tk); 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,8 +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.setVar(ControlLastResult, result)
|
ctx.UnsafeSetVar(ControlLastResult, result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// } else {
|
// } else {
|
||||||
// err = errors.New("empty expression")
|
// err = errors.New("empty expression")
|
||||||
|
59
bind-expr-functions.go
Normal file
59
bind-expr-functions.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// 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 (functor *exprFunctor) GetParams() (params []ExprFuncParam) {
|
||||||
|
return functor.params
|
||||||
|
}
|
||||||
|
|
||||||
|
func newExprFunctor(e Expr, params []ExprFuncParam, ctx ExprContext) *exprFunctor {
|
||||||
|
var defCtx ExprContext
|
||||||
|
if ctx != nil {
|
||||||
|
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) InvokeNamed(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var missing []string
|
||||||
|
for _, p := range functor.params {
|
||||||
|
if arg, exists := args[p.Name()]; exists {
|
||||||
|
if funcArg, ok := arg.(Functor); ok {
|
||||||
|
paramSpecs := funcArg.GetParams()
|
||||||
|
ctx.RegisterFunc(p.Name(), funcArg, TypeAny, paramSpecs)
|
||||||
|
} else {
|
||||||
|
ctx.UnsafeSetVar(p.Name(), arg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if missing == nil {
|
||||||
|
missing = make([]string, 0, 1)
|
||||||
|
}
|
||||||
|
missing = append(missing, p.Name())
|
||||||
|
// ctx.UnsafeSetVar(p.Name(), nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if missing != nil {
|
||||||
|
err = ErrMissingParams(name, missing)
|
||||||
|
} else {
|
||||||
|
result, err = functor.expr.Eval(ctx)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
23
bind-go-functions.go
Normal file
23
bind-go-functions.go
Normal file
@ -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) InvokeNamed(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
return functor.f(ctx, name, args)
|
||||||
|
}
|
247
builtin-base.go
Normal file
247
builtin-base.go
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtin-base.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ParamDenominator = "denominator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isNilFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
result = args[ParamValue] == nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isIntFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
result = IsInteger(args[ParamValue])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFloatFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
result = IsFloat(args[ParamValue])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBoolFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
result = IsBool(args[ParamValue])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isStringFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
result = IsString(args[ParamValue])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFractionFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
result = IsFract(args[ParamValue])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRationalFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
result = IsRational(args[ParamValue])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isListFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
result = IsList(args[ParamValue])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDictionaryFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
result = IsDict(args[ParamValue])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func boolFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
switch v := args[ParamValue].(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 map[string]any) (result any, err error) {
|
||||||
|
switch v := args[ParamValue].(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 map[string]any) (result any, err error) {
|
||||||
|
switch v := args[ParamValue].(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 map[string]any) (result any, err error) {
|
||||||
|
switch v := args[ParamValue].(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 map[string]any) (result any, err error) {
|
||||||
|
switch v := args[ParamValue].(type) {
|
||||||
|
case int64:
|
||||||
|
var den int64 = 1
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
if den, ok = args[ParamDenominator].(int64); !ok {
|
||||||
|
err = ErrExpectedGot(name, "integer", args[ParamDenominator])
|
||||||
|
} 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 evalFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
if source, ok := args[ParamSource].(string); ok {
|
||||||
|
var expr Expr
|
||||||
|
|
||||||
|
parser := NewParser()
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = NewSimpleStore()
|
||||||
|
}
|
||||||
|
|
||||||
|
r := strings.NewReader(source)
|
||||||
|
scanner := NewScanner(r, DefaultTranslations())
|
||||||
|
|
||||||
|
if expr, err = parser.Parse(scanner); err == nil {
|
||||||
|
CtrlEnable(ctx, control_export_all)
|
||||||
|
result, err = expr.Eval(ctx)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//// import
|
||||||
|
|
||||||
|
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(ParamDenominator, PfDefault, int64(1)),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("eval", NewGolangFunctor(evalFunc), TypeAny, []ExprFuncParam{
|
||||||
|
NewFuncParam(ParamSource),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterBuiltinModule("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
|
||||||
|
}
|
57
builtin-fmt.go
Normal file
57
builtin-fmt.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// 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 map[string]any) (result any, err error) {
|
||||||
|
var n int = 0
|
||||||
|
if v, exists := args[ParamItem]; exists && v != nil {
|
||||||
|
argv := v.([]any)
|
||||||
|
n, err = fmt.Fprint(getStdout(ctx), argv...)
|
||||||
|
}
|
||||||
|
result = int64(n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func printLnFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var n int = 0
|
||||||
|
if v, exists := args[ParamItem]; exists && v != nil {
|
||||||
|
argv := v.([]any)
|
||||||
|
n, err = fmt.Fprintln(getStdout(ctx), argv...)
|
||||||
|
} else {
|
||||||
|
n, err = fmt.Fprintln(getStdout(ctx))
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
}
|
78
builtin-import.go
Normal file
78
builtin-import.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// 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 map[string]any) (result any, err error) {
|
||||||
|
return importGeneral(ctx, name, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func importAllFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
CtrlEnable(ctx, control_export_all)
|
||||||
|
return importGeneral(ctx, name, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func importGeneral(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
dirList := buildSearchDirList("sources", ENV_EXPR_SOURCE_PATH)
|
||||||
|
if v, exists := args[ParamFilepath]; exists && v != nil {
|
||||||
|
argv := v.([]any)
|
||||||
|
result, err = doImport(ctx, name, dirList, NewArrayIterator(argv))
|
||||||
|
}
|
||||||
|
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, allowMultiExpr|allowVarRef, SymEos); 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()")
|
||||||
|
}
|
104
builtin-iterator.go
Normal file
104
builtin-iterator.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
// 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"
|
||||||
|
iterVarStatus = "status"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseRunArgs(localCtx ExprContext, args map[string]any) (it Iterator, op Functor, err error) {
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if it, ok = args[ParamIterator].(Iterator); !ok {
|
||||||
|
err = fmt.Errorf("paramter %q must be an iterator, passed %v [%s]", ParamIterator, args[ParamIterator], TypeName(args[ParamIterator]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if op, ok = args[iterParamOperator].(Functor); !ok && args[iterParamOperator] != nil {
|
||||||
|
err = fmt.Errorf("paramter %q must be a function, passed %v [%s]", iterParamOperator, args[iterParamOperator], TypeName(args[iterParamOperator]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var vars *DictType
|
||||||
|
if vars, ok = args[iterParamVars].(*DictType); !ok && args[iterParamVars] != nil {
|
||||||
|
err = fmt.Errorf("paramter %q must be a dictionary, passed %v [%s]", iterParamVars, args[iterParamVars], TypeName(args[iterParamVars]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if vars != nil {
|
||||||
|
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 map[string]any) (result any, err error) {
|
||||||
|
var it Iterator
|
||||||
|
var ok bool
|
||||||
|
var op Functor
|
||||||
|
var v any
|
||||||
|
var usingDefaultOp = false
|
||||||
|
var params map[string]any
|
||||||
|
var item any
|
||||||
|
|
||||||
|
localCtx := ctx.Clone()
|
||||||
|
localCtx.UnsafeSetVar(iterVarStatus, 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 = map[string]any{ParamItem: []any{item}}
|
||||||
|
} else {
|
||||||
|
params = map[string]any{ParamIndex: it.Index(), ParamItem: item}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err = op.InvokeNamed(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(iterVarStatus)
|
||||||
|
}
|
||||||
|
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 (
|
||||||
@ -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++
|
||||||
@ -34,8 +34,8 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
|
|||||||
if v, err = doAdd(ctx, name, subIter, count, level); err != nil {
|
if v, err = doAdd(ctx, name, subIter, count, level); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(cleanName) {
|
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(CleanName) {
|
||||||
if _, err = extIter.CallOperation(cleanName, nil); err != nil {
|
if _, err = extIter.CallOperation(CleanName, nil); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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)
|
||||||
}
|
}
|
||||||
@ -86,8 +86,9 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func addFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
func addFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
result, err = doAdd(ctx, name, NewArrayIterator(args), 0, -1)
|
argv := args[ParamValue].([]any)
|
||||||
|
result, err = doAdd(ctx, name, NewArrayIterator(argv), 0, -1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +96,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++
|
||||||
@ -107,8 +108,8 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
|
|||||||
if v, err = doMul(ctx, name, subIter, count, level); err != nil {
|
if v, err = doMul(ctx, name, subIter, count, level); err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(cleanName) {
|
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(CleanName) {
|
||||||
if _, err = extIter.CallOperation(cleanName, nil); err != nil {
|
if _, err = extIter.CallOperation(CleanName, nil); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,9 +137,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)
|
||||||
}
|
}
|
||||||
@ -161,16 +162,22 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
func mulFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
result, err = doMul(ctx, name, NewArrayIterator(args), 0, -1)
|
argv := args[ParamValue].([]any)
|
||||||
|
result, err = doMul(ctx, name, NewArrayIterator(argv), 0, -1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func ImportMathFuncs(ctx ExprContext) {
|
func ImportMathFuncs(ctx ExprContext) {
|
||||||
ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
|
ctx.RegisterFunc("add", NewGolangFunctor(addFunc), TypeNumber, []ExprFuncParam{
|
||||||
ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
|
NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, int64(0)),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("mul", NewGolangFunctor(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()")
|
||||||
}
|
}
|
257
builtin-os-file.go
Normal file
257
builtin-os-file.go
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtin-os-file.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
osLimitCh = "limitCh"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 map[string]any) (result any, err error) {
|
||||||
|
if filePath, ok := args[ParamFilepath].(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 map[string]any) (result any, err error) {
|
||||||
|
if filePath, ok := args[ParamFilepath].(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 map[string]any) (result any, err error) {
|
||||||
|
if filePath, ok := args[ParamFilepath].(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 map[string]any) (result any, err error) {
|
||||||
|
var handle osHandle
|
||||||
|
var invalidFileHandle any
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if handle, ok = args[ParamHandle].(osHandle); !ok {
|
||||||
|
invalidFileHandle = args[ParamHandle]
|
||||||
|
}
|
||||||
|
|
||||||
|
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 map[string]any) (result any, err error) {
|
||||||
|
var handle osHandle
|
||||||
|
var invalidFileHandle any
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if handle, ok = args[ParamHandle].(osHandle); !ok {
|
||||||
|
invalidFileHandle = args[ParamHandle]
|
||||||
|
}
|
||||||
|
|
||||||
|
if handle != nil {
|
||||||
|
if w, ok := handle.(*osWriter); ok {
|
||||||
|
if v, exists := args[ParamItem]; exists {
|
||||||
|
argv := v.([]any)
|
||||||
|
result, err = fmt.Fprint(w.writer, argv...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
invalidFileHandle = handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && (handle == nil || invalidFileHandle != nil) {
|
||||||
|
err = errInvalidFileHandle(name, invalidFileHandle)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileReadTextFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var handle osHandle
|
||||||
|
var invalidFileHandle any
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
result = nil
|
||||||
|
if handle, ok = args[ParamHandle].(osHandle); !ok || args[ParamHandle] == nil {
|
||||||
|
invalidFileHandle = args[ParamHandle]
|
||||||
|
}
|
||||||
|
|
||||||
|
if handle != nil {
|
||||||
|
if r, ok := handle.(*osReader); ok {
|
||||||
|
var limit byte = '\n'
|
||||||
|
var v string
|
||||||
|
if s, ok := args[osLimitCh].(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 map[string]any) (result any, err error) {
|
||||||
|
var handle osHandle
|
||||||
|
var invalidFileHandle any
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
result = nil
|
||||||
|
if handle, ok = args[ParamHandle].(osHandle); !ok || args[ParamHandle] == nil {
|
||||||
|
invalidFileHandle = args[ParamHandle]
|
||||||
|
}
|
||||||
|
|
||||||
|
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), TypeFileHandle, []ExprFuncParam{
|
||||||
|
NewFuncParam(ParamFilepath),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("fileAppend", NewGolangFunctor(appendFileFunc), TypeFileHandle, []ExprFuncParam{
|
||||||
|
NewFuncParam(ParamFilepath),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("fileCreate", NewGolangFunctor(createFileFunc), TypeFileHandle, []ExprFuncParam{
|
||||||
|
NewFuncParam(ParamFilepath),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("fileClose", NewGolangFunctor(closeFileFunc), TypeBoolean, []ExprFuncParam{
|
||||||
|
NewFuncParam(ParamHandle),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("fileWriteText", NewGolangFunctor(fileWriteTextFunc), TypeInt, []ExprFuncParam{
|
||||||
|
NewFuncParam(ParamHandle),
|
||||||
|
NewFuncParamFlagDef(ParamItem, PfDefault|PfRepeat, ""),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("fileReadText", NewGolangFunctor(fileReadTextFunc), TypeString, []ExprFuncParam{
|
||||||
|
NewFuncParam(ParamHandle),
|
||||||
|
NewFuncParamFlagDef(osLimitCh, PfDefault, "\n"),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("fileReadTextAll", NewGolangFunctor(fileReadTextAllFunc), TypeString, []ExprFuncParam{
|
||||||
|
NewFuncParam(ParamHandle),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterBuiltinModule("os.file", ImportOsFuncs, "Operating system file functions")
|
||||||
|
}
|
245
builtin-string.go
Normal file
245
builtin-string.go
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtin-string.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
strParamOther = "other"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- 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 map[string]any) (result any, err error) {
|
||||||
|
if sep, ok := args[ParamSeparator].(string); ok {
|
||||||
|
if v, exists := args[ParamItem]; exists {
|
||||||
|
argv := v.([]any)
|
||||||
|
if len(argv) == 1 {
|
||||||
|
if ls, ok := argv[0].(*ListType); ok {
|
||||||
|
result, err = doJoinStr(name, sep, NewListIterator(ls, nil))
|
||||||
|
} else if it, ok := argv[0].(Iterator); ok {
|
||||||
|
result, err = doJoinStr(name, sep, it)
|
||||||
|
} else if s, ok := argv[0].(string); ok {
|
||||||
|
result = s
|
||||||
|
} else {
|
||||||
|
err = ErrInvalidParameterValue(name, ParamItem, v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result, err = doJoinStr(name, sep, NewArrayIterator(argv))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = ErrWrongParamType(name, ParamSeparator, TypeString, args[ParamSeparator])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func subStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var start = 0
|
||||||
|
var count = -1
|
||||||
|
var source string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if source, ok = args[ParamSource].(string); !ok {
|
||||||
|
return nil, ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
|
||||||
|
}
|
||||||
|
|
||||||
|
if start, err = ToGoInt(args[ParamStart], name+"()"); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if count, err = ToGoInt(args[ParamCount], 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 map[string]any) (result any, err error) {
|
||||||
|
var source string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if source, ok = args[ParamSource].(string); !ok {
|
||||||
|
return nil, ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
|
||||||
|
}
|
||||||
|
result = strings.TrimSpace(source)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func startsWithStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var source, prefix string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
result = false
|
||||||
|
|
||||||
|
if source, ok = args[ParamSource].(string); !ok {
|
||||||
|
return result, ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
|
||||||
|
}
|
||||||
|
|
||||||
|
if prefix, ok = args[ParamPrefix].(string); !ok {
|
||||||
|
return result, ErrWrongParamType(name, ParamPrefix, TypeString, args[ParamPrefix])
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(source, prefix) {
|
||||||
|
result = true
|
||||||
|
} else if v, exists := args[strParamOther]; exists {
|
||||||
|
argv := v.([]any)
|
||||||
|
for i, targetSpec := range argv {
|
||||||
|
if target, ok := targetSpec.(string); ok {
|
||||||
|
if strings.HasPrefix(source, target) {
|
||||||
|
result = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("target item nr %d is %s, string expected", i+1, TypeName(targetSpec))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func endsWithStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var source, suffix string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
result = false
|
||||||
|
|
||||||
|
if source, ok = args[ParamSource].(string); !ok {
|
||||||
|
return result, ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
|
||||||
|
}
|
||||||
|
|
||||||
|
if suffix, ok = args[ParamSuffix].(string); !ok {
|
||||||
|
return result, ErrWrongParamType(name, ParamSuffix, TypeString, args[ParamSuffix])
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(source, suffix) {
|
||||||
|
result = true
|
||||||
|
} else if v, exists := args[strParamOther]; exists {
|
||||||
|
argv := v.([]any)
|
||||||
|
for i, targetSpec := range argv {
|
||||||
|
if target, ok := targetSpec.(string); ok {
|
||||||
|
if strings.HasSuffix(source, target) {
|
||||||
|
result = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("target item nr %d is %s, string expected", i+1, TypeName(targetSpec))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
|
||||||
|
var source, sep string
|
||||||
|
var count int = -1
|
||||||
|
var parts []string
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if source, ok = args[ParamSource].(string); !ok {
|
||||||
|
return result, ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
|
||||||
|
}
|
||||||
|
|
||||||
|
if sep, ok = args[ParamSeparator].(string); !ok {
|
||||||
|
return nil, fmt.Errorf("separator param must be string, got %s (%v)", TypeName(args[ParamSeparator]), args[ParamSeparator])
|
||||||
|
}
|
||||||
|
|
||||||
|
if count64, ok := args[ParamCount].(int64); ok { // TODO replace type assertion with toInt()
|
||||||
|
count = int(count64)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("part count must be integer, got %s (%v)", TypeName(args[ParamCount]), args[ParamCount])
|
||||||
|
}
|
||||||
|
|
||||||
|
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(strParamOther, PfRepeat),
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.RegisterFunc("strEndsWith", NewGolangFunctor(endsWithStrFunc), TypeBoolean, []ExprFuncParam{
|
||||||
|
NewFuncParam(ParamSource),
|
||||||
|
NewFuncParam(ParamSuffix),
|
||||||
|
NewFuncParamFlag(strParamOther, 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")
|
||||||
|
}
|
48
builtins-register.go
Normal file
48
builtins-register.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// builtins-register.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type builtinModule struct {
|
||||||
|
importFunc func(ExprContext)
|
||||||
|
description string
|
||||||
|
imported bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBuiltinModule(importFunc func(ExprContext), description string) *builtinModule {
|
||||||
|
return &builtinModule{importFunc, description, false}
|
||||||
|
}
|
||||||
|
|
||||||
|
var builtinModuleRegister map[string]*builtinModule
|
||||||
|
|
||||||
|
func RegisterBuiltinModule(name string, importFunc func(ExprContext), description string) {
|
||||||
|
if builtinModuleRegister == nil {
|
||||||
|
builtinModuleRegister = make(map[string]*builtinModule)
|
||||||
|
}
|
||||||
|
if _, exists := builtinModuleRegister[name]; exists {
|
||||||
|
panic(fmt.Errorf("module %q already registered", name))
|
||||||
|
}
|
||||||
|
builtinModuleRegister[name] = newBuiltinModule(importFunc, description)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IterateBuiltinModules(op func(name, description string, imported bool) bool) {
|
||||||
|
if op != nil {
|
||||||
|
for name, mod := range builtinModuleRegister {
|
||||||
|
if !op(name, mod.description, mod.imported) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----
|
||||||
|
func init() {
|
||||||
|
if builtinModuleRegister == nil {
|
||||||
|
builtinModuleRegister = make(map[string]*builtinModule)
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -6,50 +6,82 @@ package expr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func errTooFewParams(minArgs, maxArgs, argCount int) (err error) {
|
func ErrMissingParams(funcName string, missing []string) (err error) {
|
||||||
|
return fmt.Errorf("%s(): missing params -- %s", funcName, strings.Join(missing, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error) {
|
||||||
if maxArgs < 0 {
|
if maxArgs < 0 {
|
||||||
err = fmt.Errorf("too few params -- expected %d or more, got %d", minArgs, argCount)
|
err = fmt.Errorf("%s(): too few params -- expected %d or more, got %d", funcName, minArgs, argCount)
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("too few params -- expected %d, got %d", minArgs, argCount)
|
err = fmt.Errorf("%s(): too few params -- expected %d, got %d", funcName, minArgs, argCount)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func errTooMuchParams(maxArgs, argCount int) (err error) {
|
func ErrTooManyParams(funcName string, maxArgs, argCount int) (err error) {
|
||||||
err = fmt.Errorf("too much params -- expected %d, got %d", maxArgs, argCount)
|
err = fmt.Errorf("%s(): too many params -- expected %d, got %d", funcName, maxArgs, argCount)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- General errors
|
// --- General errors
|
||||||
|
|
||||||
func errCantConvert(funcName string, value any, kind string) error {
|
func ErrCantConvert(funcName string, value any, kind string) error {
|
||||||
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 errDivisionByZero(funcName string) error {
|
func ErrFuncDivisionByZero(funcName string) error {
|
||||||
return fmt.Errorf("%s() division by zero", funcName)
|
return fmt.Errorf("%s(): division by zero", funcName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func ErrDivisionByZero() error {
|
||||||
|
// return fmt.Errorf("division by zero")
|
||||||
|
// }
|
||||||
|
|
||||||
// --- Parameter errors
|
// --- Parameter errors
|
||||||
|
|
||||||
// func errOneParam(funcName string) error {
|
// func ErrMissingRequiredParameter(funcName, paramName string) error {
|
||||||
// return fmt.Errorf("%s() requires exactly one param", funcName)
|
// return fmt.Errorf("%s(): missing required parameter %q", funcName, paramName)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func errMissingRequiredParameter(funcName, paramName string) error {
|
func ErrInvalidParameterValue(funcName, paramName string, paramValue any) error {
|
||||||
return fmt.Errorf("%s() missing required parameter %q", funcName, paramName)
|
return fmt.Errorf("%s(): invalid value %s (%v) for parameter %q", funcName, TypeName(paramValue), paramValue, paramName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func errInvalidParameterValue(funcName, paramName string, paramValue any) error {
|
func undefArticle(s string) (article string) {
|
||||||
return fmt.Errorf("%s() invalid value %T (%v) for parameter %q", funcName, paramValue, paramValue, paramName)
|
if len(s) > 0 && strings.Contains("aeiou", s[0:1]) {
|
||||||
|
article = "an"
|
||||||
|
} else {
|
||||||
|
article = "a"
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func errWrongParamType(funcName, paramName, paramType string, paramValue any) error {
|
func prependUndefArticle(s string) (result string) {
|
||||||
return fmt.Errorf("%s() the %q parameter must be a %s, got a %T (%v)", funcName, paramName, paramType, paramValue, paramValue)
|
return undefArticle(s) + " " + s
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrWrongParamType(funcName, paramName, paramType string, paramValue any) error {
|
||||||
|
var artWantType, artGotType string
|
||||||
|
gotType := TypeName(paramValue)
|
||||||
|
artGotType = prependUndefArticle(gotType)
|
||||||
|
artWantType = prependUndefArticle(paramType)
|
||||||
|
return fmt.Errorf("%s(): the %q parameter must be %s, got %s (%v)", funcName, paramName, artWantType, artGotType, paramValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrUnknownParam(funcName, paramName string) error {
|
||||||
|
return fmt.Errorf("%s(): unknown parameter %q", funcName, paramName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Operator errors
|
||||||
|
|
||||||
|
func ErrLeftOperandMustBeVariable(leftTerm, opTerm *term) error {
|
||||||
|
return leftTerm.Errorf("left operand of %q must be a variable", opTerm.source())
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,28 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
const (
|
const (
|
||||||
paramParts = "parts"
|
ParamArgs = "args"
|
||||||
paramSeparator = "separator"
|
ParamCount = "count"
|
||||||
paramSource = "source"
|
ParamItem = "item"
|
||||||
|
ParamIndex = "index"
|
||||||
|
ParamParts = "parts"
|
||||||
|
ParamSeparator = "separator"
|
||||||
|
ParamSource = "source"
|
||||||
|
ParamSuffix = "suffix"
|
||||||
|
ParamPrefix = "prefix"
|
||||||
|
ParamStart = "start"
|
||||||
|
ParamEnd = "end"
|
||||||
|
ParamValue = "value"
|
||||||
|
ParamName = "name"
|
||||||
|
ParamEllipsis = "..."
|
||||||
|
ParamFilepath = "filepath"
|
||||||
|
ParamDirpath = "dirpath"
|
||||||
|
ParamHandle = "handle"
|
||||||
|
ParamResource = "resource"
|
||||||
|
ParamIterator = "iterator"
|
||||||
|
)
|
||||||
|
|
||||||
|
// to be moved in its own source file
|
||||||
|
const (
|
||||||
|
ConstLastIndex = 0xFFFF_FFFF
|
||||||
)
|
)
|
||||||
|
@ -5,10 +5,18 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
const (
|
const (
|
||||||
typeBoolean = "boolean"
|
TypeAny = "any"
|
||||||
typeFloat = "decimal"
|
TypeNil = "nil"
|
||||||
typeFraction = "fraction"
|
TypeBoolean = "boolean"
|
||||||
typeInt = "integer"
|
TypeFloat = "float"
|
||||||
typeNumber = "number"
|
TypeFraction = "fraction"
|
||||||
typeString = "string"
|
TypeFileHandle = "file-handle"
|
||||||
|
TypeInt = "integer"
|
||||||
|
TypeItem = "item"
|
||||||
|
TypeNumber = "number"
|
||||||
|
TypePair = "pair"
|
||||||
|
TypeString = "string"
|
||||||
|
TypeDict = "dict"
|
||||||
|
TypeListOf = "list-of-"
|
||||||
|
TypeListOfStrings = "list-of-strings"
|
||||||
)
|
)
|
||||||
|
@ -15,21 +15,21 @@ 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] == '@') && !(name[0] == '_') }) {
|
||||||
// fmt.Printf("\tExporting %q\n", refName)
|
// fmt.Printf("\tExporting %q\n", refName)
|
||||||
refValue, _ := sourceCtx.GetVar(refName)
|
refValue, _ := sourceCtx.GetVar(refName)
|
||||||
exportVar(destCtx, refName, refValue)
|
exportVar(destCtx, refName, refValue)
|
||||||
@ -41,3 +41,9 @@ func exportObjects(destCtx, sourceCtx ExprContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func exportObjectsToParent(sourceCtx ExprContext) {
|
||||||
|
if parentCtx := sourceCtx.GetParent(); parentCtx != nil {
|
||||||
|
exportObjects(parentCtx, sourceCtx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
42
context.go
42
context.go
@ -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)
|
|
||||||
}
|
|
58
control.go
58
control.go
@ -4,13 +4,14 @@
|
|||||||
// control.go
|
// control.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
// Preset control variables
|
// Preset control variables
|
||||||
const (
|
const (
|
||||||
ControlLastResult = "last"
|
ControlPreset = "_preset"
|
||||||
ControlBoolShortcut = "_bool_shortcut"
|
ControlLastResult = "last"
|
||||||
ControlImportPath = "_import_path"
|
ControlBoolShortcut = "_bool_shortcut"
|
||||||
|
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)
|
return
|
||||||
}
|
|
||||||
|
|
||||||
func enable(ctx ExprContext, name string) {
|
|
||||||
if strings.HasPrefix(name, "_") {
|
|
||||||
ctx.SetVar(name, true)
|
|
||||||
} else {
|
|
||||||
ctx.SetVar("_"+name, true)
|
|
||||||
}
|
}
|
||||||
}
|
ctx.UnsafeSetVar(ControlPreset, true)
|
||||||
|
ctx.UnsafeSetVar(ControlBoolShortcut, true)
|
||||||
func disable(ctx ExprContext, name string) {
|
ctx.UnsafeSetVar(ControlSearchPath, init_search_path)
|
||||||
if strings.HasPrefix(name, "_") {
|
|
||||||
ctx.SetVar(name, false)
|
|
||||||
} else {
|
|
||||||
ctx.SetVar("_"+name, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isEnabled(ctx ExprContext, name string) (status bool) {
|
|
||||||
if v, exists := ctx.GetVar(name); exists {
|
|
||||||
if b, ok := v.(bool); ok {
|
|
||||||
status = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getControlString(ctx ExprContext, name string) (s string, exists bool) {
|
|
||||||
var v any
|
|
||||||
if v, exists = ctx.GetVar(name); exists {
|
|
||||||
s, exists = v.(string)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
291
data-cursor.go
291
data-cursor.go
@ -5,30 +5,51 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
|
"slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
type dataCursor struct {
|
type dataCursor struct {
|
||||||
ds map[string]Functor
|
ds map[string]Functor
|
||||||
ctx ExprContext
|
ctx ExprContext
|
||||||
index int
|
initState bool // true if no item has produced yet (this replace di initial Next() call in the contructor)
|
||||||
resource any
|
// cursorValid bool // true if resource is nil or if clean has not yet been called
|
||||||
nextFunc Functor
|
index int
|
||||||
cleanFunc Functor
|
count int
|
||||||
resetFunc Functor
|
current any
|
||||||
currentFunc Functor
|
lastErr error
|
||||||
|
resource any
|
||||||
|
nextFunc Functor
|
||||||
|
cleanFunc Functor
|
||||||
|
resetFunc Functor
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDataCursor(ctx ExprContext, ds map[string]Functor) (dc *dataCursor) {
|
func NewDataCursor(ctx ExprContext, ds map[string]Functor, resource any) (dc *dataCursor) {
|
||||||
dc = &dataCursor{
|
dc = &dataCursor{
|
||||||
ds: ds,
|
ds: ds,
|
||||||
index: -1,
|
initState: true,
|
||||||
ctx: ctx.Clone(),
|
// cursorValid: true,
|
||||||
|
index: -1,
|
||||||
|
count: 0,
|
||||||
|
current: nil,
|
||||||
|
lastErr: nil,
|
||||||
|
resource: resource,
|
||||||
|
ctx: ctx.Clone(),
|
||||||
|
nextFunc: ds[NextName],
|
||||||
|
cleanFunc: ds[CleanName],
|
||||||
|
resetFunc: ds[ResetName],
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) Context() ExprContext {
|
||||||
|
return dc.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
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('{')
|
||||||
@ -58,7 +79,7 @@ func (dc *dataCursor) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dc *dataCursor) HasOperation(name string) (exists bool) {
|
func (dc *dataCursor) HasOperation(name string) (exists bool) {
|
||||||
exists = name == indexName
|
exists = slices.Contains([]string{CleanName, ResetName, CurrentName, IndexName}, name)
|
||||||
if !exists {
|
if !exists {
|
||||||
f, ok := dc.ds[name]
|
f, ok := dc.ds[name]
|
||||||
exists = ok && isFunctor(f)
|
exists = ok && isFunctor(f)
|
||||||
@ -66,89 +87,219 @@ func (dc *dataCursor) HasOperation(name string) (exists bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc *dataCursor) CallOperation(name string, args []any) (value any, err error) {
|
func (dc *dataCursor) CallOperation(name string, args map[string]any) (value any, err error) {
|
||||||
if name == indexName {
|
if name == IndexName {
|
||||||
value = int64(dc.Index())
|
value = int64(dc.Index())
|
||||||
|
} else if name == CleanName {
|
||||||
|
err = dc.Clean()
|
||||||
|
} else if name == ResetName {
|
||||||
|
err = dc.Reset()
|
||||||
} else if functor, ok := dc.ds[name]; ok && isFunctor(functor) {
|
} else if functor, ok := dc.ds[name]; ok && isFunctor(functor) {
|
||||||
if functor == dc.cleanFunc {
|
ctx := cloneContext(dc.ctx)
|
||||||
value, err = dc.Clean()
|
value, err = functor.InvokeNamed(ctx, name, args)
|
||||||
} else if functor == dc.resetFunc {
|
exportObjects(dc.ctx, ctx)
|
||||||
value, err = dc.Reset()
|
|
||||||
} else {
|
|
||||||
ctx := cloneContext(dc.ctx)
|
|
||||||
value, err = functor.Invoke(ctx, name, []any{})
|
|
||||||
exportObjects(dc.ctx, ctx)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
err = errNoOperation(name)
|
err = errNoOperation(name)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc *dataCursor) Reset() (success bool, err error) {
|
// func (dc *dataCursor) Reset() (err error) {
|
||||||
|
// if dc.resetFunc != nil {
|
||||||
|
// if dc.resource != nil {
|
||||||
|
// ctx := cloneContext(dc.ctx)
|
||||||
|
// actualParams := bindActualParams(dc.resetFunc, []any{dc.resource})
|
||||||
|
// _, err = dc.resetFunc.InvokeNamed(ctx, ResetName, actualParams)
|
||||||
|
// exportObjects(dc.ctx, ctx)
|
||||||
|
// dc.index = -1
|
||||||
|
// dc.count = 0
|
||||||
|
// dc.initState = true
|
||||||
|
// dc.current = nil
|
||||||
|
// dc.lastErr = nil
|
||||||
|
// } else {
|
||||||
|
// err = errInvalidDataSource()
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// err = errNoOperation(ResetName)
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (dc *dataCursor) Reset() (err error) {
|
||||||
if dc.resetFunc != nil {
|
if dc.resetFunc != nil {
|
||||||
if dc.resource != nil {
|
ctx := cloneContext(dc.ctx)
|
||||||
ctx := cloneContext(dc.ctx)
|
actualParams := bindActualParams(dc.resetFunc, []any{dc.resource})
|
||||||
if _, err = dc.resetFunc.Invoke(ctx, resetName, []any{dc.resource}); err == nil {
|
_, err = dc.resetFunc.InvokeNamed(ctx, ResetName, actualParams)
|
||||||
dc.index = -1
|
exportObjects(dc.ctx, ctx)
|
||||||
}
|
|
||||||
exportObjects(dc.ctx, ctx)
|
|
||||||
} else {
|
|
||||||
err = errInvalidDataSource()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = errNoOperation(resetName)
|
|
||||||
}
|
}
|
||||||
success = err == nil
|
dc.index = -1
|
||||||
|
dc.count = 0
|
||||||
|
dc.initState = true
|
||||||
|
dc.current = nil
|
||||||
|
dc.lastErr = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc *dataCursor) Clean() (success bool, err error) {
|
func (dc *dataCursor) Clean() (err error) {
|
||||||
if dc.cleanFunc != nil {
|
if dc.cleanFunc != nil {
|
||||||
if dc.resource != nil {
|
ctx := cloneContext(dc.ctx)
|
||||||
ctx := cloneContext(dc.ctx)
|
actualParams := bindActualParams(dc.cleanFunc, []any{dc.resource})
|
||||||
_, err = dc.cleanFunc.Invoke(ctx, cleanName, []any{dc.resource})
|
_, err = dc.cleanFunc.InvokeNamed(ctx, CleanName, actualParams)
|
||||||
dc.resource = nil
|
exportObjects(dc.ctx, ctx)
|
||||||
exportObjects(dc.ctx, ctx)
|
|
||||||
} else {
|
|
||||||
err = errInvalidDataSource()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = errors.New("no 'clean' function defined in the data-source")
|
|
||||||
}
|
}
|
||||||
success = err == nil
|
dc.lastErr = io.EOF
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func (dc *dataCursor) Clean() (err error) {
|
||||||
|
// if dc.cleanFunc != nil {
|
||||||
|
// if dc.resource != nil {
|
||||||
|
// ctx := cloneContext(dc.ctx)
|
||||||
|
// actualParams := bindActualParams(dc.cleanFunc, []any{dc.resource})
|
||||||
|
// _, err = dc.cleanFunc.InvokeNamed(ctx, CleanName, actualParams)
|
||||||
|
// exportObjects(dc.ctx, ctx)
|
||||||
|
// } else {
|
||||||
|
// err = errInvalidDataSource()
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// err = errNoOperation(CleanName)
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
|
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
|
||||||
ctx := cloneContext(dc.ctx)
|
dc.init()
|
||||||
if item, err = dc.currentFunc.Invoke(ctx, currentName, []any{}); err == nil && item == nil {
|
|
||||||
|
if dc.current != nil {
|
||||||
|
item = dc.current
|
||||||
|
} else {
|
||||||
err = io.EOF
|
err = io.EOF
|
||||||
}
|
}
|
||||||
exportObjects(dc.ctx, ctx)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item
|
func (dc *dataCursor) checkFilter(filter Functor, item any) (accepted bool, err error) {
|
||||||
if dc.resource != nil {
|
var v any
|
||||||
ctx := cloneContext(dc.ctx)
|
var ok bool
|
||||||
// fmt.Printf("Entering Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
|
ctx := cloneContext(dc.ctx)
|
||||||
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil {
|
|
||||||
if item == nil {
|
actualParams := bindActualParams(filter, []any{item, dc.index})
|
||||||
err = io.EOF
|
if v, err = filter.InvokeNamed(ctx, FilterName, actualParams); err == nil && v != nil {
|
||||||
} else {
|
if accepted, ok = v.(bool); !ok {
|
||||||
dc.index++
|
accepted = true // NOTE: A non-boolean value that is not nil means the item has been accepted
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc *dataCursor) Index() int {
|
func (dc *dataCursor) mapItem(mapper Functor, item any) (mappedItem any, err error) {
|
||||||
return dc.index
|
ctx := cloneContext(dc.ctx)
|
||||||
|
actualParams := bindActualParams(mapper, []any{item, dc.index})
|
||||||
|
mappedItem, err = mapper.InvokeNamed(ctx, MapName, actualParams)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) init() {
|
||||||
|
if dc.initState {
|
||||||
|
dc.initState = false
|
||||||
|
dc.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) Next() (current any, err error) { // must return io.EOF after the last item
|
||||||
|
if dc.initState {
|
||||||
|
dc.init()
|
||||||
|
} else if err = dc.lastErr; err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
current = dc.current
|
||||||
|
filter := dc.ds[FilterName]
|
||||||
|
mapper := dc.ds[MapName]
|
||||||
|
var item any
|
||||||
|
for item == nil && dc.lastErr == nil {
|
||||||
|
ctx := cloneContext(dc.ctx)
|
||||||
|
dc.index++
|
||||||
|
|
||||||
|
actualParams := bindActualParams(dc.nextFunc, []any{dc.resource, dc.index})
|
||||||
|
if item, dc.lastErr = dc.nextFunc.InvokeNamed(ctx, NextName, actualParams); dc.lastErr == nil {
|
||||||
|
if item == nil {
|
||||||
|
dc.lastErr = io.EOF
|
||||||
|
} else {
|
||||||
|
accepted := true
|
||||||
|
if filter != nil {
|
||||||
|
if accepted, dc.lastErr = dc.checkFilter(filter, item); dc.lastErr != nil || !accepted {
|
||||||
|
item = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if accepted {
|
||||||
|
dc.count++
|
||||||
|
}
|
||||||
|
if item != nil && mapper != nil {
|
||||||
|
item, dc.lastErr = dc.mapItem(mapper, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exportObjects(dc.ctx, ctx)
|
||||||
|
}
|
||||||
|
dc.current = item
|
||||||
|
if dc.lastErr != nil {
|
||||||
|
dc.index--
|
||||||
|
dc.Clean()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (dc *dataCursor) Next() (current any, err error) { // must return io.EOF after the last item
|
||||||
|
// if dc.initState {
|
||||||
|
// dc.init()
|
||||||
|
// } else if err = dc.lastErr; err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// current = dc.current
|
||||||
|
// if dc.resource != nil {
|
||||||
|
// filter := dc.ds[FilterName]
|
||||||
|
// mapper := dc.ds[MapName]
|
||||||
|
// var item any
|
||||||
|
// for item == nil && dc.lastErr == nil {
|
||||||
|
// ctx := cloneContext(dc.ctx)
|
||||||
|
// dc.index++
|
||||||
|
|
||||||
|
// actualParams := bindActualParams(dc.nextFunc, []any{dc.resource, dc.index})
|
||||||
|
// if item, dc.lastErr = dc.nextFunc.InvokeNamed(ctx, NextName, actualParams); dc.lastErr == nil {
|
||||||
|
// if item == nil {
|
||||||
|
// dc.lastErr = io.EOF
|
||||||
|
// } else {
|
||||||
|
// accepted := true
|
||||||
|
// if filter != nil {
|
||||||
|
// if accepted, dc.lastErr = dc.checkFilter(filter, item); dc.lastErr != nil || !accepted {
|
||||||
|
// item = nil
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if accepted {
|
||||||
|
// dc.count++
|
||||||
|
// }
|
||||||
|
// if item != nil && mapper != nil {
|
||||||
|
// item, dc.lastErr = dc.mapItem(mapper, item)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// exportObjects(dc.ctx, ctx)
|
||||||
|
// }
|
||||||
|
// dc.current = item
|
||||||
|
// if dc.lastErr != nil {
|
||||||
|
// dc.index--
|
||||||
|
// dc.Clean()
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// dc.lastErr = errInvalidDataSource()
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (dc *dataCursor) Index() int {
|
||||||
|
return dc.index - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dataCursor) Count() int {
|
||||||
|
return dc.count
|
||||||
}
|
}
|
||||||
|
166
dict-type.go
Normal file
166
dict-type.go
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
// 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]any) (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 newDict(dictAny map[any]*term) (dict *DictType) {
|
||||||
|
// TODO Change with a call to NewDict()
|
||||||
|
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
|
||||||
|
}
|
779
doc/Expr.adoc
779
doc/Expr.adoc
File diff suppressed because it is too large
Load Diff
1370
doc/Expr.html
1370
doc/Expr.html
File diff suppressed because one or more lines are too long
29
expr-context.go
Normal file
29
expr-context.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// expr-context.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
// ----Expression Context
|
||||||
|
type ExprContext interface {
|
||||||
|
Clone() 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 map[string]any) (result any, err error)
|
||||||
|
RegisterFuncInfo(info ExprFunc)
|
||||||
|
RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) (funcInfo ExprFunc, err error)
|
||||||
|
}
|
39
expr-function.go
Normal file
39
expr-function.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// expr-function.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
// ---- Functor interface
|
||||||
|
type Functor interface {
|
||||||
|
Typer
|
||||||
|
InvokeNamed(ctx ExprContext, name string, args map[string]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
|
||||||
|
ParamSpec(paramName string) ExprFuncParam
|
||||||
|
ReturnType() string
|
||||||
|
PrepareCall(name string, actualParams map[string]any) (err error)
|
||||||
|
AllocContext(parentCtx ExprContext) (ctx ExprContext)
|
||||||
|
}
|
12
expr.go
Normal file
12
expr.go
Normal file
@ -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
|
||||||
|
}
|
82
expr_test.go
82
expr_test.go
@ -1,82 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// expr_test.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestExpr(t *testing.T) {
|
|
||||||
type inputType struct {
|
|
||||||
source string
|
|
||||||
wantResult any
|
|
||||||
wantErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
inputs := []inputType{
|
|
||||||
/* 1 */ {`0?{}`, nil, nil},
|
|
||||||
/* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
|
|
||||||
/* 3 */ {`f=openFile("test-file.txt"); line=readFile(f); closeFile(f); line`, "uno", nil},
|
|
||||||
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
|
|
||||||
/* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil},
|
|
||||||
/* 10 */ {`
|
|
||||||
ds={
|
|
||||||
"init":func(end){@end=end; @current=0 but true},
|
|
||||||
"current":func(){current},
|
|
||||||
"next":func(){
|
|
||||||
((next=current+1) <= end) ? [true] {@current=next but current} :: {nil}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
it=$(ds,3);
|
|
||||||
it++;
|
|
||||||
it++
|
|
||||||
`, int64(1), nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
succeeded := 0
|
|
||||||
failed := 0
|
|
||||||
|
|
||||||
for i, input := range inputs {
|
|
||||||
var expr Expr
|
|
||||||
var gotResult any
|
|
||||||
var gotErr error
|
|
||||||
|
|
||||||
ctx := NewSimpleFuncStore()
|
|
||||||
// ImportMathFuncs(ctx)
|
|
||||||
// ImportImportFunc(ctx)
|
|
||||||
ImportOsFuncs(ctx)
|
|
||||||
parser := NewParser(ctx)
|
|
||||||
|
|
||||||
logTest(t, i+1, "Expr", input.source, input.wantResult, input.wantErr)
|
|
||||||
|
|
||||||
r := strings.NewReader(input.source)
|
|
||||||
scanner := NewScanner(r, DefaultTranslations())
|
|
||||||
|
|
||||||
good := true
|
|
||||||
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
|
|
||||||
gotResult, gotErr = expr.Eval(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
if gotResult != input.wantResult {
|
|
||||||
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
|
||||||
good = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if gotErr != input.wantErr {
|
|
||||||
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
|
|
||||||
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
|
|
||||||
good = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if good {
|
|
||||||
succeeded++
|
|
||||||
} else {
|
|
||||||
failed++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.Logf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed)
|
|
||||||
}
|
|
@ -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)
|
|
61
formatter.go
61
formatter.go
@ -4,17 +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
|
Base2
|
||||||
Base8
|
Base8
|
||||||
Base10
|
Base10
|
||||||
Base16
|
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
|
||||||
|
}
|
||||||
|
361
fraction-type.go
Normal file
361
fraction-type.go
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
// 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 sign, num string
|
||||||
|
if f.num < 0 && opt&TTY == 0 {
|
||||||
|
num = strconv.FormatInt(-f.num, 10)
|
||||||
|
sign = "-"
|
||||||
|
} else {
|
||||||
|
num = strconv.FormatInt(f.num, 10)
|
||||||
|
}
|
||||||
|
den := strconv.FormatInt(f.den, 10)
|
||||||
|
size := max(len(num), len(den))
|
||||||
|
if opt&TTY != 0 {
|
||||||
|
sNum := fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, sign+num))
|
||||||
|
sb.WriteString(sNum)
|
||||||
|
} else {
|
||||||
|
if len(sign) > 0 {
|
||||||
|
sb.WriteString(" ")
|
||||||
|
}
|
||||||
|
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, num)))
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
if len(sign) > 0 {
|
||||||
|
sb.WriteString(sign)
|
||||||
|
sb.WriteByte(' ')
|
||||||
|
}
|
||||||
|
sb.WriteString(strings.Repeat("-", size))
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
if len(sign) > 0 {
|
||||||
|
sb.WriteString(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sDen := fmt.Sprintf("%[1]*s", size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den))
|
||||||
|
sb.WriteString(sDen)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
175
func-base.go
175
func-base.go
@ -1,175 +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) {
|
|
||||||
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 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 *fraction:
|
|
||||||
result = v.toFloat()
|
|
||||||
default:
|
|
||||||
err = errCantConvert(name, v, "float")
|
|
||||||
}
|
|
||||||
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 = errDivisionByZero(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
result = newFraction(v, den)
|
|
||||||
}
|
|
||||||
case float64:
|
|
||||||
result, err = float64ToFraction(v)
|
|
||||||
// var n, d int64
|
|
||||||
// if n, d, err = float64ToFraction(v); err == nil {
|
|
||||||
// result = newFraction(n, d)
|
|
||||||
// }
|
|
||||||
case bool:
|
|
||||||
if v {
|
|
||||||
result = newFraction(1, 1)
|
|
||||||
} else {
|
|
||||||
result = newFraction(0, 1)
|
|
||||||
}
|
|
||||||
case string:
|
|
||||||
result, err = makeGeneratingFraction(v)
|
|
||||||
// var f float64
|
|
||||||
// // TODO temporary implementation
|
|
||||||
// if f, err = strconv.ParseFloat(v, 64); err == nil {
|
|
||||||
// var n, d int64
|
|
||||||
// if n, d, err = float64ToFraction(f); err == nil {
|
|
||||||
// result = newFraction(n, d)
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// errors.New("convertion from string to float is ongoing")
|
|
||||||
// }
|
|
||||||
case *fraction:
|
|
||||||
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) {
|
|
||||||
ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, 1)
|
|
||||||
ctx.RegisterFunc("isInt", &simpleFunctor{f: isIntFunc}, 1, 1)
|
|
||||||
ctx.RegisterFunc("isFloat", &simpleFunctor{f: isFloatFunc}, 1, 1)
|
|
||||||
ctx.RegisterFunc("isBool", &simpleFunctor{f: isBoolFunc}, 1, 1)
|
|
||||||
ctx.RegisterFunc("isString", &simpleFunctor{f: isStringFunc}, 1, 1)
|
|
||||||
ctx.RegisterFunc("isFraction", &simpleFunctor{f: isFractionFunc}, 1, 1)
|
|
||||||
ctx.RegisterFunc("isFract", &simpleFunctor{f: isFractionFunc}, 1, 1)
|
|
||||||
ctx.RegisterFunc("isRational", &simpleFunctor{f: isRationalFunc}, 1, 1)
|
|
||||||
ctx.RegisterFunc("isList", &simpleFunctor{f: isListFunc}, 1, 1)
|
|
||||||
ctx.RegisterFunc("isDictionary", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
|
|
||||||
ctx.RegisterFunc("isDict", &simpleFunctor{f: isDictionaryFunc}, 1, 1)
|
|
||||||
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, 1)
|
|
||||||
ctx.RegisterFunc("dec", &simpleFunctor{f: decFunc}, 1, 1)
|
|
||||||
ctx.RegisterFunc("fract", &simpleFunctor{f: fractFunc}, 1, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
registerImport("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
|
|
||||||
}
|
|
146
func-import.go
146
func-import.go
@ -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
func-os.go
171
func-os.go
@ -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")
|
|
||||||
}
|
|
211
func-string.go
211
func-string.go
@ -1,211 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// func-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 == 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func startsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
|
||||||
var source string
|
|
||||||
var ok bool
|
|
||||||
|
|
||||||
result = false
|
|
||||||
// if len(args) < 1 {
|
|
||||||
// return result, errMissingRequiredParameter(name, paramSource)
|
|
||||||
// }
|
|
||||||
if source, ok = args[0].(string); !ok {
|
|
||||||
return result, errWrongParamType(name, paramSource, typeString, args[0])
|
|
||||||
}
|
|
||||||
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 len(args) < 1 {
|
|
||||||
// return result, errMissingRequiredParameter(name, paramSource)
|
|
||||||
// }
|
|
||||||
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 len(args) < 1 {
|
|
||||||
// return result, errMissingRequiredParameter(name, paramSource)
|
|
||||||
// }
|
|
||||||
if source, ok = args[0].(string); !ok {
|
|
||||||
return result, errWrongParamType(name, paramSource, typeString, args[0])
|
|
||||||
}
|
|
||||||
if len(args) >= 2 {
|
|
||||||
if sep, ok = args[1].(string); !ok {
|
|
||||||
return nil, fmt.Errorf("separator param must be string, got %T (%v)", args[1], args[1])
|
|
||||||
}
|
|
||||||
if len(args) >= 3 {
|
|
||||||
if count64, ok := args[2].(int64); ok { // TODO replace type assertion with toInt()
|
|
||||||
count = int(count64)
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("part count must be integer, got %T (%v)", args[2], args[2])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if count > 0 {
|
|
||||||
parts = strings.SplitN(source, sep, count)
|
|
||||||
} else if count < 0 {
|
|
||||||
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("joinStr", &simpleFunctor{f: joinStrFunc}, 1, -1)
|
|
||||||
ctx.RegisterFunc("subStr", &simpleFunctor{f: subStrFunc}, 1, -1)
|
|
||||||
ctx.RegisterFunc("splitStr", &simpleFunctor{f: splitStrFunc}, 2, -1)
|
|
||||||
ctx.RegisterFunc("trimStr", &simpleFunctor{f: trimStrFunc}, 1, -1)
|
|
||||||
ctx.RegisterFunc("startsWithStr", &simpleFunctor{f: startsWithStrFunc}, 2, -1)
|
|
||||||
ctx.RegisterFunc("endsWithStr", &simpleFunctor{f: endsWithStrFunc}, 2, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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,89 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// funcs_test.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFuncs(t *testing.T) {
|
|
||||||
inputs := []inputType{
|
|
||||||
/* 1 */ {`isNil(nil)`, true, nil},
|
|
||||||
/* 2 */ {`v=nil; isNil(v)`, true, nil},
|
|
||||||
/* 3 */ {`v=5; isNil(v)`, false, nil},
|
|
||||||
/* 4 */ {`int(true)`, int64(1), nil},
|
|
||||||
/* 5 */ {`int(false)`, int64(0), nil},
|
|
||||||
/* 6 */ {`int(3.1)`, int64(3), nil},
|
|
||||||
/* 7 */ {`int(3.9)`, int64(3), nil},
|
|
||||||
/* 8 */ {`int("432")`, int64(432), nil},
|
|
||||||
/* 9 */ {`int("1.5")`, nil, errors.New(`strconv.Atoi: parsing "1.5": invalid syntax`)},
|
|
||||||
/* 10 */ {`int("432", 4)`, nil, errors.New(`too much params -- expected 1, got 2`)},
|
|
||||||
/* 11 */ {`int(nil)`, nil, errors.New(`int() can't convert <nil> to int`)},
|
|
||||||
/* 12 */ {`two=func(){2}; two()`, int64(2), nil},
|
|
||||||
/* 13 */ {`double=func(x) {2*x}; (double(3))`, int64(6), nil},
|
|
||||||
/* 14 */ {`double=func(x){2*x}; double(3)`, int64(6), nil},
|
|
||||||
/* 15 */ {`double=func(x){2*x}; a=5; double(3+a) + 1`, int64(17), nil},
|
|
||||||
/* 16 */ {`double=func(x){2*x}; a=5; two=func() {2}; (double(3+a) + 1) * two()`, int64(34), nil},
|
|
||||||
/* 17 */ {`builtin "import"; import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
|
||||||
/* 18 */ {`builtin "import"; import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
|
|
||||||
/* 19 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
|
|
||||||
/* 20 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
|
|
||||||
/* 21 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
|
|
||||||
/* 22 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable or function "x"`)},
|
|
||||||
/* 23 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
|
||||||
/* 24 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
|
||||||
/* 25 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
|
|
||||||
/* 26 */ {`builtin "import"; importAll("./test-funcs.expr"); six()`, int64(6), nil},
|
|
||||||
/* 27 */ {`builtin "import"; import("./sample-export-all.expr"); six()`, int64(6), nil},
|
|
||||||
/* 28 */ {`builtin "string"; joinStr("-", "one", "two", "three")`, "one-two-three", nil},
|
|
||||||
/* 29 */ {`builtin "string"; joinStr("-", ["one", "two", "three"])`, "one-two-three", nil},
|
|
||||||
/* 30 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr("-", ls)`, "one-two-three", nil},
|
|
||||||
/* 31 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr(1, ls)`, nil, errors.New(`joinStr() the "separator" parameter must be a string, got a int64 (1)`)},
|
|
||||||
/* 32 */ {`builtin "string"; ls= ["one", 2, "three"]; joinStr("-", ls)`, nil, errors.New(`joinStr() expected string, got int64 (2)`)},
|
|
||||||
/* 33 */ {`builtin "string"; "<"+trimStr(" bye bye ")+">"`, "<bye bye>", nil},
|
|
||||||
/* 34 */ {`builtin "string"; subStr("0123456789", 1,2)`, "12", nil},
|
|
||||||
/* 35 */ {`builtin "string"; subStr("0123456789", -3,2)`, "78", nil},
|
|
||||||
/* 36 */ {`builtin "string"; subStr("0123456789", -3)`, "789", nil},
|
|
||||||
/* 37 */ {`builtin "string"; subStr("0123456789")`, "0123456789", nil},
|
|
||||||
/* 38 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "012")`, true, nil},
|
|
||||||
/* 39 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "0125")`, false, nil},
|
|
||||||
/* 40 */ {`builtin "string"; startsWithStr("0123456789")`, nil, errors.New(`too few params -- expected 2 or more, got 1`)},
|
|
||||||
/* 41 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "789")`, true, nil},
|
|
||||||
/* 42 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "0125")`, false, nil},
|
|
||||||
/* 43 */ {`builtin "string"; endsWithStr("0123456789")`, nil, errors.New(`too few params -- expected 2 or more, got 1`)},
|
|
||||||
/* 44 */ {`builtin "string"; splitStr("one-two-three", "-", )`, newListA("one", "two", "three"), nil},
|
|
||||||
/* 45 */ {`isInt(2+1)`, true, nil},
|
|
||||||
/* 46 */ {`isInt(3.1)`, false, nil},
|
|
||||||
/* 47 */ {`isFloat(3.1)`, true, nil},
|
|
||||||
/* 48 */ {`isString("3.1")`, true, nil},
|
|
||||||
/* 49 */ {`isString("3" + 1)`, true, nil},
|
|
||||||
/* 50 */ {`isList(["3", 1])`, true, nil},
|
|
||||||
/* 51 */ {`isDict({"a":"3", "b":1})`, true, nil},
|
|
||||||
/* 52 */ {`isFract(1|3)`, true, nil},
|
|
||||||
/* 53 */ {`isFract(3|1)`, false, nil},
|
|
||||||
/* 54 */ {`isRational(3|1)`, true, nil},
|
|
||||||
/* 55 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil},
|
|
||||||
/* 56 */ {`fract("2.2(3)")`, newFraction(67, 30), nil},
|
|
||||||
/* 57 */ {`fract("1.21(3)")`, newFraction(91, 75), nil},
|
|
||||||
/* 58 */ {`fract(1.21(3))`, newFraction(91, 75), nil},
|
|
||||||
/* 59 */ {`fract(1.21)`, newFraction(121, 100), nil},
|
|
||||||
/* 60 */ {`dec(2)`, float64(2), nil},
|
|
||||||
/* 61 */ {`dec(2.0)`, float64(2), nil},
|
|
||||||
/* 62 */ {`dec("2.0")`, float64(2), nil},
|
|
||||||
/* 63 */ {`dec(true)`, float64(1), nil},
|
|
||||||
/* 64 */ {`dec(true")`, nil, errors.New("[1:11] missing string termination \"")},
|
|
||||||
/* 65 */ {`builtin "string"; joinStr("-", [1, "two", "three"])`, nil, errors.New(`joinStr() expected string, got int64 (1)`)},
|
|
||||||
/* 65 */ {`dec()`, nil, errors.New(`too few params -- expected 1, got 0`)},
|
|
||||||
/* 66 */ {`dec(1,2,3)`, nil, errors.New(`too much params -- expected 1, got 3`)},
|
|
||||||
/* 67 */ {`builtin "string"; joinStr()`, nil, errors.New(`too few params -- expected 1 or more, got 0`)},
|
|
||||||
// /* 64 */ {`string(true)`, "true", nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Setenv("EXPR_PATH", ".")
|
|
||||||
|
|
||||||
//parserTest(t, "Func", inputs[54:55])
|
|
||||||
parserTest(t, "Func", inputs)
|
|
||||||
}
|
|
386
function.go
Normal file
386
function.go
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// function.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---- Function template
|
||||||
|
type FuncTemplate func(ctx ExprContext, name string, args map[string]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 (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) ParamSpec(paramName string) ExprFuncParam {
|
||||||
|
for _, spec := range info.formalParams {
|
||||||
|
if spec.Name() == paramName {
|
||||||
|
return spec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initActualParams(ctx ExprContext, info ExprFunc, callTerm *term) (actualParams map[string]any, err error) {
|
||||||
|
var varArgs []any
|
||||||
|
var varName string
|
||||||
|
|
||||||
|
namedParamsStarted := false
|
||||||
|
|
||||||
|
formalParams := info.Params()
|
||||||
|
actualParams = make(map[string]any, len(formalParams))
|
||||||
|
if callTerm == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tree := range callTerm.children {
|
||||||
|
var paramValue any
|
||||||
|
paramCtx := ctx.Clone()
|
||||||
|
if paramValue, err = tree.compute(paramCtx); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if paramName, namedParam := getAssignVarName(tree); namedParam {
|
||||||
|
if info.ParamSpec(paramName) == nil {
|
||||||
|
err = fmt.Errorf("%s(): unknown param %q", info.Name(), paramName)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
actualParams[paramName] = paramValue
|
||||||
|
namedParamsStarted = true
|
||||||
|
} else if !namedParamsStarted {
|
||||||
|
if varArgs != nil {
|
||||||
|
varArgs = append(varArgs, paramValue)
|
||||||
|
} else if i < len(formalParams) {
|
||||||
|
spec := formalParams[i]
|
||||||
|
if spec.IsRepeat() {
|
||||||
|
varArgs = make([]any, 0, len(callTerm.children)-i)
|
||||||
|
varArgs = append(varArgs, paramValue)
|
||||||
|
varName = spec.Name()
|
||||||
|
} else {
|
||||||
|
actualParams[spec.Name()] = paramValue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = ErrTooManyParams(info.Name(), len(formalParams), len(callTerm.children))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("%s(): positional param nr %d not allowed after named params", info.Name(), i+1)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
if varArgs != nil {
|
||||||
|
actualParams[varName] = varArgs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *funcInfo) PrepareCall(name string, actualParams map[string]any) (err error) {
|
||||||
|
passedCount := len(actualParams)
|
||||||
|
if info.MinArgs() > passedCount {
|
||||||
|
err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if passedCount < len(info.formalParams) {
|
||||||
|
for _, p := range info.formalParams {
|
||||||
|
if _, exists := actualParams[p.Name()]; !exists {
|
||||||
|
if !p.IsDefault() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if p.IsRepeat() {
|
||||||
|
varArgs := make([]any, 1)
|
||||||
|
varArgs[0] = p.DefaultValue()
|
||||||
|
actualParams[p.Name()] = varArgs
|
||||||
|
} else {
|
||||||
|
actualParams[p.Name()] = p.DefaultValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.MaxArgs() >= 0 && info.MaxArgs() < len(actualParams) {
|
||||||
|
err = ErrTooManyParams(name, info.MaxArgs(), len(actualParams))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Call a function ---
|
||||||
|
|
||||||
|
func getAssignVarName(t *term) (name string, ok bool) {
|
||||||
|
if ok = t.symbol() == SymEqual; ok {
|
||||||
|
name = t.children[0].source()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func CallFunctionByTerm(parentCtx ExprContext, name string, callTerm *term) (result any, err error) {
|
||||||
|
var actualParams map[string]any
|
||||||
|
if info, exists := GetFuncInfo(parentCtx, name); exists {
|
||||||
|
if actualParams, err = initActualParams(parentCtx, info, callTerm); err == nil {
|
||||||
|
ctx := info.AllocContext(parentCtx)
|
||||||
|
if err = info.PrepareCall(name, actualParams); err == nil {
|
||||||
|
functor := info.Functor()
|
||||||
|
result, err = functor.InvokeNamed(ctx, name, actualParams)
|
||||||
|
exportObjectsToParent(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("unknown function %s()", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func CallFunctionByArgs(parentCtx ExprContext, name string, args []any) (result any, err error) {
|
||||||
|
var actualParams map[string]any
|
||||||
|
if info, exists := GetFuncInfo(parentCtx, name); exists {
|
||||||
|
functor := info.Functor()
|
||||||
|
actualParams = bindActualParams(functor, args)
|
||||||
|
ctx := info.AllocContext(parentCtx)
|
||||||
|
if err = info.PrepareCall(name, actualParams); err == nil {
|
||||||
|
result, err = functor.InvokeNamed(ctx, name, actualParams)
|
||||||
|
exportObjectsToParent(ctx)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("unknown function %s()", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func CallFunctionByParams(parentCtx ExprContext, name string, actualParams map[string]any) (result any, err error) {
|
||||||
|
//var actualParams map[string]any
|
||||||
|
if info, exists := GetFuncInfo(parentCtx, name); exists {
|
||||||
|
functor := info.Functor()
|
||||||
|
ctx := info.AllocContext(parentCtx)
|
||||||
|
if err = info.PrepareCall(name, actualParams); err == nil {
|
||||||
|
result, err = functor.InvokeNamed(ctx, name, actualParams)
|
||||||
|
exportObjectsToParent(ctx)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("unknown function %s()", name)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetParam(args map[string]any, paramName string, paramNum int) (value any, exists bool) {
|
||||||
|
if value, exists = args[paramName]; !exists {
|
||||||
|
if paramNum > 0 && paramNum <= len(args) {
|
||||||
|
value, exists = args["arg"+strconv.Itoa(paramNum)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindActualParams(functor Functor, args []any) (actualParams map[string]any) {
|
||||||
|
formalParams := functor.GetParams()
|
||||||
|
actualParams = make(map[string]any, len(args))
|
||||||
|
for i, arg := range args {
|
||||||
|
if i < len(formalParams) {
|
||||||
|
actualParams[formalParams[i].Name()] = arg
|
||||||
|
} else {
|
||||||
|
actualParams["arg"+strconv.Itoa(i+1)] = arg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
@ -4,13 +4,16 @@
|
|||||||
// global-context.go
|
// global-context.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import "path/filepath"
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
var globalCtx *SimpleFuncStore
|
var globalCtx *SimpleStore
|
||||||
|
|
||||||
func ImportInContext(name string) (exists bool) {
|
func ImportInContext(name string) (exists bool) {
|
||||||
var mod *module
|
var mod *builtinModule
|
||||||
if mod, exists = moduleRegister[name]; exists {
|
if mod, exists = builtinModuleRegister[name]; exists {
|
||||||
mod.importFunc(globalCtx)
|
mod.importFunc(globalCtx)
|
||||||
mod.imported = true
|
mod.imported = true
|
||||||
}
|
}
|
||||||
@ -19,7 +22,7 @@ func ImportInContext(name string) (exists bool) {
|
|||||||
|
|
||||||
func ImportInContextByGlobPattern(pattern string) (count int, err error) {
|
func ImportInContextByGlobPattern(pattern string) (count int, err error) {
|
||||||
var matched bool
|
var matched bool
|
||||||
for name, mod := range moduleRegister {
|
for name, mod := range builtinModuleRegister {
|
||||||
if matched, err = filepath.Match(pattern, name); err == nil {
|
if matched, err = filepath.Match(pattern, name); err == nil {
|
||||||
if matched {
|
if matched {
|
||||||
count++
|
count++
|
||||||
@ -40,15 +43,114 @@ func GetVar(ctx ExprContext, name string) (value any, exists bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool, ownerCtx ExprContext) {
|
func GetLocalFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool) {
|
||||||
if item, exists = ctx.GetFuncInfo(name); exists {
|
var v any
|
||||||
ownerCtx = ctx
|
if len(name) > 0 {
|
||||||
} else if item, exists = globalCtx.GetFuncInfo(name); exists {
|
if v, exists = ctx.GetVar(name); exists && isFunctor(v) {
|
||||||
ownerCtx = globalCtx
|
f, _ := v.(Functor)
|
||||||
|
item = f.GetFunc()
|
||||||
|
} else {
|
||||||
|
item, exists = ctx.GetFuncInfo(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool) {
|
||||||
|
// if len(name) > 0 {
|
||||||
|
// if item, exists = GetLocalFuncInfo(ctx, name); exists {
|
||||||
|
// ownerCtx = ctx
|
||||||
|
// } else if item, exists = globalCtx.GetFuncInfo(name); exists {
|
||||||
|
// ownerCtx = globalCtx
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
item, exists, _ = GetFuncInfoAndOwner(ctx, name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFuncInfoAndOwner(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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
globalCtx = NewSimpleFuncStore()
|
globalCtx = NewSimpleStore()
|
||||||
|
initDefaultVars(globalCtx)
|
||||||
|
ImportBuiltinsFuncs(globalCtx)
|
||||||
}
|
}
|
||||||
|
6
go.mod
6
go.mod
@ -1,3 +1,7 @@
|
|||||||
module git.portale-stac.it/go-pkg/expr
|
module git.portale-stac.it/go-pkg/expr
|
||||||
|
|
||||||
go 1.21.6
|
go 1.22.0
|
||||||
|
|
||||||
|
toolchain go1.23.3
|
||||||
|
|
||||||
|
require golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d
|
||||||
|
4
go.sum
Normal file
4
go.sum
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
github.com/yqylovy/goimportdot v0.0.0-20170519021755-eb181a7eeabe h1:bWYrKmmfv37uNgXTdwkLSKYiYPJ1yfWmjBnvtMyAYzk=
|
||||||
|
github.com/yqylovy/goimportdot v0.0.0-20170519021755-eb181a7eeabe/go.mod h1:alTKUpAJ/zbp17qvZwcFNwzufrb5DljMDY4mgJlIHao=
|
||||||
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
|
||||||
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
15
helpers.go
15
helpers.go
@ -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)
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// helpers_test.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func subtract(ctx ExprContext, name string, args []any) (result any, err error) {
|
|
||||||
if len(args) != 2 {
|
|
||||||
err = fmt.Errorf("%s(): requires exactly two arguments", name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
x, xok := args[0].(int64)
|
|
||||||
y, yok := args[1].(int64)
|
|
||||||
if xok && yok {
|
|
||||||
result = x - y
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("expected integer (int64) arguments, got %T and %T values", x, y)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvalStringA(t *testing.T) {
|
|
||||||
|
|
||||||
source := `a + b * subtract(4,2)`
|
|
||||||
args := []Arg{
|
|
||||||
{"a", uint8(1)},
|
|
||||||
{"b", int8(2)},
|
|
||||||
{"subtract", FuncTemplate(subtract)},
|
|
||||||
// force coverage
|
|
||||||
{"a16", uint16(1)},
|
|
||||||
{"b16", int16(2)},
|
|
||||||
{"a32", uint32(1)},
|
|
||||||
{"b32", int32(2)},
|
|
||||||
{"a64", uint64(1)},
|
|
||||||
{"b64", int64(2)},
|
|
||||||
{"f32", float32(1.0)},
|
|
||||||
{"f64", float64(1.0)},
|
|
||||||
}
|
|
||||||
|
|
||||||
wantResult := int64(5)
|
|
||||||
gotResult, gotErr := EvalStringA(source, args...)
|
|
||||||
if value, ok := gotResult.(int64); ok && value != wantResult {
|
|
||||||
t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult)
|
|
||||||
t.Errorf("Error: %v", gotErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEvalString(t *testing.T) {
|
|
||||||
|
|
||||||
ctx := NewSimpleVarStore()
|
|
||||||
ctx.SetVar("a", uint8(1))
|
|
||||||
ctx.SetVar("b", int8(2))
|
|
||||||
ctx.SetVar("f", 2.0)
|
|
||||||
// force coverage
|
|
||||||
ctx.SetVar("a16", uint16(1))
|
|
||||||
ctx.SetVar("b16", int16(2))
|
|
||||||
ctx.SetVar("a32", uint32(1))
|
|
||||||
ctx.SetVar("b32", int32(2))
|
|
||||||
ctx.SetVar("a64", uint64(1))
|
|
||||||
ctx.SetVar("b64", int64(2))
|
|
||||||
ctx.SetVar("f32", float32(1.0))
|
|
||||||
ctx.SetVar("f64", float64(1.0))
|
|
||||||
|
|
||||||
// force coverage
|
|
||||||
ctx.GetFuncInfo("dummy")
|
|
||||||
ctx.Call("dummy", []any{})
|
|
||||||
|
|
||||||
source := `a + b * f`
|
|
||||||
|
|
||||||
wantResult := float64(5)
|
|
||||||
gotResult, gotErr := EvalString(ctx, source)
|
|
||||||
if value, ok := gotResult.(float64); ok && value != wantResult {
|
|
||||||
t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult)
|
|
||||||
t.Errorf("Error: %v", gotErr)
|
|
||||||
}
|
|
||||||
}
|
|
112
import-utils.go
Normal file
112
import-utils.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
// 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) {
|
||||||
|
var err error
|
||||||
|
for _, dir := range dirList {
|
||||||
|
if dir, err = ExpandPath(dir); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
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 filename, err = ExpandPath(filename); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
32
iterator.go
32
iterator.go
@ -5,38 +5,44 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
// "errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Operator names
|
// Operator names
|
||||||
|
|
||||||
const (
|
const (
|
||||||
initName = "init"
|
InitName = "init"
|
||||||
cleanName = "clean"
|
CleanName = "clean"
|
||||||
resetName = "reset"
|
ResetName = "reset"
|
||||||
nextName = "next"
|
NextName = "next"
|
||||||
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
|
||||||
|
Count() int
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExtIterator interface {
|
type ExtIterator interface {
|
||||||
Iterator
|
Iterator
|
||||||
|
Reset() error
|
||||||
|
Clean() error
|
||||||
HasOperation(name string) bool
|
HasOperation(name string) bool
|
||||||
CallOperation(name string, args []any) (value any, err error)
|
CallOperation(name string, args map[string]any) (value any, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
return errors.New("invalid data-source")
|
// return errors.New("invalid data-source")
|
||||||
}
|
// }
|
||||||
|
@ -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,18 +85,28 @@ 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 map[string]any) (v any, err error) {
|
||||||
switch name {
|
switch name {
|
||||||
case resetName:
|
case NextName:
|
||||||
v, err = it.Reset()
|
v, err = it.Next()
|
||||||
case indexName:
|
case ResetName:
|
||||||
|
err = it.Reset()
|
||||||
|
case CleanName:
|
||||||
|
err = it.Clean()
|
||||||
|
case IndexName:
|
||||||
v = int64(it.Index())
|
v = int64(it.Index())
|
||||||
case countName:
|
case CurrentName:
|
||||||
|
v, err = it.Current()
|
||||||
|
case CountName:
|
||||||
v = it.count
|
v = it.count
|
||||||
default:
|
default:
|
||||||
err = errNoOperation(name)
|
err = errNoOperation(name)
|
||||||
@ -106,10 +116,19 @@ 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 {
|
||||||
item = a[it.index]
|
if it.stop < len(a) && it.index >= it.start && it.index <= it.stop {
|
||||||
|
item = a[it.index]
|
||||||
|
} else {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
err = io.EOF
|
if it.start < len(a) && it.index >= it.stop && it.index <= it.start {
|
||||||
|
item = a[it.index]
|
||||||
|
} else {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -126,7 +145,16 @@ func (it *ListIterator) Index() int {
|
|||||||
return it.index
|
return it.index
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *ListIterator) Reset() (bool, error) {
|
func (it *ListIterator) Count() int {
|
||||||
it.index = it.start
|
return it.count
|
||||||
return true, nil
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) Reset() (error) {
|
||||||
|
it.index = it.start - it.step
|
||||||
|
it.count = 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *ListIterator) Clean() (error) {
|
||||||
|
return nil
|
||||||
}
|
}
|
195
list-type.go
Normal file
195
list-type.go
Normal file
@ -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
|
||||||
|
}
|
||||||
|
|
120
list_test.go
120
list_test.go
@ -1,120 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// list_test.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestListParser(t *testing.T) {
|
|
||||||
section := "List"
|
|
||||||
|
|
||||||
type inputType struct {
|
|
||||||
source string
|
|
||||||
wantResult any
|
|
||||||
wantErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
inputs := []inputType{
|
|
||||||
/* 1 */ {`[]`, []any{}, nil},
|
|
||||||
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
|
||||||
/* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil},
|
|
||||||
/* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil},
|
|
||||||
/* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
|
||||||
/* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil},
|
|
||||||
/* 7 */ {`add([1,4,3,2])`, int64(10), nil},
|
|
||||||
/* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
|
|
||||||
/* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
|
|
||||||
/* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 (2 in 1) has wrong type string, number expected`)},
|
|
||||||
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
|
|
||||||
/* 12 */ {`[1,2,3] << 2+2`, []any{int64(1), int64(2), int64(3), int64(4)}, nil},
|
|
||||||
/* 13 */ {`2-1 >> [2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
|
|
||||||
/* 14 */ {`[1,2,3].1`, int64(2), nil},
|
|
||||||
/* 15 */ {`ls=[1,2,3] but ls.1`, int64(2), nil},
|
|
||||||
/* 16 */ {`ls=[1,2,3] but ls.(-1)`, int64(3), nil},
|
|
||||||
/* 17 */ {`list=["one","two","three"]; list.10`, nil, errors.New(`[1:36] index 10 out of bounds`)},
|
|
||||||
/* 18 */ {`["a", "b", "c"]`, newListA("a", "b", "c"), nil},
|
|
||||||
/* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil},
|
|
||||||
/* 20 */ {`#["a", "b", "c"]`, int64(3), nil},
|
|
||||||
/* 21 */ {`"b" in ["a", "b", "c"]`, true, nil},
|
|
||||||
|
|
||||||
// /* 8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, nil},
|
|
||||||
// /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
succeeded := 0
|
|
||||||
failed := 0
|
|
||||||
|
|
||||||
// inputs1 := []inputType{
|
|
||||||
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
|
|
||||||
// }
|
|
||||||
|
|
||||||
for i, input := range inputs {
|
|
||||||
var expr *ast
|
|
||||||
var gotResult any
|
|
||||||
var gotErr error
|
|
||||||
|
|
||||||
ctx := NewSimpleFuncStore()
|
|
||||||
// ctx.SetVar("var1", int64(123))
|
|
||||||
// ctx.SetVar("var2", "abc")
|
|
||||||
ImportMathFuncs(ctx)
|
|
||||||
parser := NewParser(ctx)
|
|
||||||
|
|
||||||
logTest(t, i+1, "List", input.source, input.wantResult, input.wantErr)
|
|
||||||
|
|
||||||
r := strings.NewReader(input.source)
|
|
||||||
scanner := NewScanner(r, DefaultTranslations())
|
|
||||||
|
|
||||||
good := true
|
|
||||||
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
|
|
||||||
gotResult, gotErr = expr.Eval(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gotResult == nil && input.wantResult != nil) || (gotResult != nil && input.wantResult == nil) {
|
|
||||||
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
|
||||||
good = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if gotList, okGot := gotResult.([]any); okGot {
|
|
||||||
if wantList, okWant := input.wantResult.([]any); okWant {
|
|
||||||
if (gotList == nil && wantList != nil) || (gotList != nil && wantList == nil) {
|
|
||||||
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
|
||||||
good = false
|
|
||||||
} else {
|
|
||||||
equal := len(gotList) == len(wantList)
|
|
||||||
if equal {
|
|
||||||
for i, gotItem := range gotList {
|
|
||||||
wantItem := wantList[i]
|
|
||||||
equal = gotItem == wantItem
|
|
||||||
if !equal {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !equal {
|
|
||||||
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
|
||||||
good = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if gotErr != input.wantErr {
|
|
||||||
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
|
|
||||||
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
|
|
||||||
good = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if good {
|
|
||||||
succeeded++
|
|
||||||
} else {
|
|
||||||
failed++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// module-register.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type module struct {
|
|
||||||
importFunc func(ExprContext)
|
|
||||||
description string
|
|
||||||
imported bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newModule(importFunc func(ExprContext), description string) *module {
|
|
||||||
return &module{importFunc, description, false}
|
|
||||||
}
|
|
||||||
|
|
||||||
var moduleRegister map[string]*module
|
|
||||||
|
|
||||||
func registerImport(name string, importFunc func(ExprContext), description string) {
|
|
||||||
if moduleRegister == nil {
|
|
||||||
moduleRegister = make(map[string]*module)
|
|
||||||
}
|
|
||||||
if _, exists := moduleRegister[name]; exists {
|
|
||||||
panic(fmt.Errorf("module %q already registered", name))
|
|
||||||
}
|
|
||||||
moduleRegister[name] = newModule(importFunc, description)
|
|
||||||
}
|
|
||||||
|
|
||||||
// func ImportInContext(ctx ExprContext, name string) (exists bool) {
|
|
||||||
// var mod *module
|
|
||||||
// if mod, exists = moduleRegister[name]; exists {
|
|
||||||
// mod.importFunc(ctx)
|
|
||||||
// mod.imported = true
|
|
||||||
// }
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) {
|
|
||||||
// var matched bool
|
|
||||||
// for name, mod := range moduleRegister {
|
|
||||||
// if matched, err = filepath.Match(pattern, name); err == nil {
|
|
||||||
// if matched {
|
|
||||||
// count++
|
|
||||||
// mod.importFunc(ctx)
|
|
||||||
// mod.imported = true
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
func IterateModules(op func(name, description string, imported bool) bool) {
|
|
||||||
if op != nil {
|
|
||||||
for name, mod := range moduleRegister {
|
|
||||||
if !op(name, mod.description, mod.imported) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----
|
|
||||||
func init() {
|
|
||||||
if moduleRegister == nil {
|
|
||||||
moduleRegister = make(map[string]*module)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
102
operand-func.go
102
operand-func.go
@ -6,7 +6,6 @@ package expr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// -------- function call term
|
// -------- function call term
|
||||||
@ -22,42 +21,26 @@ 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, owner := GetFuncInfo(ctx, name); exists {
|
// name, _ := opTerm.tk.Value.(string)
|
||||||
if info.MinArgs() > len(params) {
|
// params := make([]any, len(opTerm.children), len(opTerm.children)+5)
|
||||||
err = errTooFewParams(info.MinArgs(), info.MaxArgs(), len(params))
|
// for i, tree := range opTerm.children {
|
||||||
}
|
// var param any
|
||||||
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(params) {
|
// if param, err = tree.compute(ctx); err != nil {
|
||||||
err = errTooMuchParams(info.MaxArgs(), len(params))
|
// break
|
||||||
}
|
// }
|
||||||
if err == nil && owner != ctx {
|
// params[i] = param
|
||||||
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
|
// }
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("unknown function %s()", name)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
|
// if err == nil {
|
||||||
ctx := cloneContext(parentCtx)
|
// v, err = CallFunction(ctx, name, params)
|
||||||
name, _ := self.tk.Value.(string)
|
// }
|
||||||
// fmt.Printf("Call %s(), context: %p\n", name, ctx)
|
// return
|
||||||
params := make([]any, len(self.children))
|
// }
|
||||||
for i, tree := range self.children {
|
|
||||||
var param any
|
func evalFuncCall(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
if param, err = tree.compute(ctx); err != nil {
|
name, _ := opTerm.tk.Value.(string)
|
||||||
break
|
v, err = CallFunctionByTerm(ctx, name, opTerm)
|
||||||
}
|
|
||||||
params[i] = param
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
if err = checkFunctionCall(ctx, name, params); err == nil {
|
|
||||||
if v, err = ctx.Call(name, params); err == nil {
|
|
||||||
exportObjects(parentCtx, ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,40 +57,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
|
|
||||||
expr Expr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (functor *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
|
|
||||||
for i, p := range functor.params {
|
|
||||||
if i < len(args) {
|
|
||||||
arg := args[i]
|
|
||||||
if functor, ok := arg.(Functor); ok {
|
|
||||||
ctx.RegisterFunc(p, functor, 0, -1)
|
|
||||||
} else {
|
|
||||||
ctx.setVar(p, arg)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctx.setVar(p, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result, err = functor.expr.eval(ctx, false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
|
|
||||||
bodySpec := self.value()
|
|
||||||
if expr, ok := bodySpec.(*ast); ok {
|
if expr, ok := bodySpec.(*ast); ok {
|
||||||
paramList := make([]string, 0, len(self.children))
|
paramList := make([]ExprFuncParam, 0, len(opTerm.children))
|
||||||
for _, param := range self.children {
|
for _, param := range opTerm.children {
|
||||||
paramList = append(paramList, param.source())
|
var defValue any
|
||||||
}
|
flags := paramFlags(0)
|
||||||
v = &funcDefFunctor{
|
if len(param.children) > 0 {
|
||||||
params: paramList,
|
flags |= PfDefault
|
||||||
expr: expr,
|
if defValue, err = param.children[0].compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info := NewFuncParamFlagDef(param.source(), flags, defValue)
|
||||||
|
paramList = append(paramList, info)
|
||||||
}
|
}
|
||||||
|
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")
|
||||||
}
|
}
|
||||||
|
@ -5,29 +5,12 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// -------- iterator term
|
// -------- iterator term
|
||||||
|
|
||||||
func newDsIteratorTerm(tk *Token, dsTerm *term, args []*term) *term {
|
|
||||||
tk.Sym = SymIterator
|
|
||||||
|
|
||||||
children := make([]*term, 0, 1+len(args))
|
|
||||||
children = append(children, dsTerm)
|
|
||||||
children = append(children, args...)
|
|
||||||
return &term{
|
|
||||||
tk: *tk,
|
|
||||||
parent: nil,
|
|
||||||
children: children,
|
|
||||||
position: posLeaf,
|
|
||||||
priority: priValue,
|
|
||||||
evalFunc: evalIterator,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newIteratorTerm(tk *Token, args []*term) *term {
|
func newIteratorTerm(tk *Token, args []*term) *term {
|
||||||
tk.Sym = SymIterator
|
tk.Sym = SymIterator
|
||||||
return &term{
|
return &term{
|
||||||
@ -42,9 +25,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 +38,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{NextName}
|
||||||
fieldsMask := 0b11
|
fieldsMask := 0b1
|
||||||
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,80 +72,65 @@ 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 = 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
|
||||||
}
|
}
|
||||||
|
|
||||||
if ds != nil {
|
if ds != nil {
|
||||||
dc := newDataCursor(ctx, ds)
|
var dc *dataCursor
|
||||||
if initFunc, exists := ds[initName]; exists && initFunc != nil {
|
dcCtx := ctx.Clone()
|
||||||
|
if initFunc, exists := ds[InitName]; exists && initFunc != nil {
|
||||||
var args []any
|
var args []any
|
||||||
if len(self.children) > 1 {
|
var resource any
|
||||||
if args, err = evalTermArray(ctx, self.children[1:]); err != nil {
|
if len(opTerm.children) > 1 {
|
||||||
|
if args, err = evalTermArray(ctx, opTerm.children[1:]); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
args = []any{}
|
args = []any{}
|
||||||
}
|
}
|
||||||
|
|
||||||
initCtx := dc.ctx.Clone()
|
actualParams := bindActualParams(initFunc, args)
|
||||||
if dc.resource, err = initFunc.Invoke(initCtx, initName, args); err != nil {
|
|
||||||
|
initCtx := ctx.Clone()
|
||||||
|
if resource, err = initFunc.InvokeNamed(initCtx, InitName, actualParams); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
exportObjects(dc.ctx, initCtx)
|
exportObjects(dcCtx, initCtx)
|
||||||
|
dc = NewDataCursor(dcCtx, ds, resource)
|
||||||
|
} else {
|
||||||
|
dc = NewDataCursor(dcCtx, ds, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
dc.nextFunc, _ = ds[nextName]
|
|
||||||
dc.currentFunc, _ = ds[currentName]
|
|
||||||
dc.cleanFunc, _ = ds[cleanName]
|
|
||||||
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 {
|
||||||
|
@ -4,83 +4,14 @@
|
|||||||
// operand-list.go
|
// operand-list.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newListA(listAny ...any) (list *ListType) {
|
|
||||||
return newList(listAny)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newList(listAny []any) (list *ListType) {
|
|
||||||
if listAny != nil {
|
|
||||||
ls := make(ListType, len(listAny))
|
|
||||||
for i, item := range listAny {
|
|
||||||
ls[i] = item
|
|
||||||
}
|
|
||||||
list = &ls
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (list *ListType) indexDeepCmp(target any) (index int) {
|
|
||||||
index = -1
|
|
||||||
for i, item := range *list {
|
|
||||||
if reflect.DeepEqual(item, target) {
|
|
||||||
index = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- list term
|
// -------- 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,
|
||||||
@ -90,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
|
||||||
|
@ -17,8 +17,8 @@ func newLiteralTerm(tk *Token) *term {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -------- eval func
|
// -------- eval func
|
||||||
func evalLiteral(ctx ExprContext, self *term) (v any, err error) {
|
func evalLiteral(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
v = self.tk.Value
|
v = opTerm.tk.Value
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 = GetVar(ctx, name); !exists {
|
if v, exists = GetVar(ctx, name); !exists {
|
||||||
if info, exists, _ := GetFuncInfo(ctx, 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)
|
||||||
|
@ -16,35 +16,189 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
rightChild := self.children[1]
|
switch collection := collectionValue.(type) {
|
||||||
|
case *ListType:
|
||||||
|
if index, ok := keyValue.(int64); ok {
|
||||||
|
err = collection.setItem(index, value)
|
||||||
|
} else {
|
||||||
|
err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, TypeName(keyValue))
|
||||||
|
}
|
||||||
|
case *DictType:
|
||||||
|
err = collection.setItem(keyValue, value)
|
||||||
|
default:
|
||||||
|
err = collectionTerm.Errorf("collection expected")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func assignValue(ctx ExprContext, leftTerm *term, v any) (err error) {
|
||||||
|
if leftTerm.symbol() == SymIndex {
|
||||||
|
err = assignCollectionItem(ctx, leftTerm.children[0], leftTerm.children[1], v)
|
||||||
|
} else {
|
||||||
|
ctx.UnsafeSetVar(leftTerm.source(), v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalAssign(ctx ExprContext, 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 v, err = rightChild.compute(ctx); err == nil {
|
||||||
if functor, ok := v.(Functor); ok {
|
if functor, ok := v.(Functor); ok {
|
||||||
var minArgs, maxArgs int = 0, -1
|
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 })
|
||||||
|
|
||||||
funcName := rightChild.source()
|
ctx.RegisterFunc(leftTerm.source(), functor, TypeAny, paramSpecs)
|
||||||
if info, exists := ctx.GetFuncInfo(funcName); exists {
|
} else {
|
||||||
minArgs = info.MinArgs()
|
err = opTerm.Errorf("unknown function %s()", rightChild.source())
|
||||||
maxArgs = info.MaxArgs()
|
}
|
||||||
} else if funcDef, ok := functor.(*funcDefFunctor); ok {
|
} else {
|
||||||
l := len(funcDef.params)
|
err = assignValue(ctx, leftTerm, v)
|
||||||
minArgs = l
|
|
||||||
maxArgs = l
|
|
||||||
}
|
}
|
||||||
ctx.RegisterFunc(leftTerm.source(), functor, minArgs, maxArgs)
|
|
||||||
} else {
|
} else {
|
||||||
ctx.setVar(leftTerm.source(), v)
|
err = assignValue(ctx, leftTerm, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
v = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------- assign term
|
||||||
|
|
||||||
|
func newOpAssignTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priAssign,
|
||||||
|
evalFunc: evalOpAssign,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCollectionItemValue(ctx ExprContext, collectionTerm, keyListTerm *term) (value any, err error) {
|
||||||
|
var collectionValue, keyListValue, keyValue any
|
||||||
|
var keyList *ListType
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if collectionValue, err = collectionTerm.compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if keyListValue, err = keyListTerm.compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
} else if keyList, ok = keyListValue.(*ListType); !ok || len(*keyList) != 1 {
|
||||||
|
err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, TypeName(keyListValue))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if keyValue = (*keyList)[0]; keyValue == nil {
|
||||||
|
err = keyListTerm.Errorf("index/key is nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch collection := collectionValue.(type) {
|
||||||
|
case *ListType:
|
||||||
|
if index, ok := keyValue.(int64); ok {
|
||||||
|
value = (*collection)[index]
|
||||||
|
} else {
|
||||||
|
err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, TypeName(keyValue))
|
||||||
|
}
|
||||||
|
case *DictType:
|
||||||
|
value = (*collection)[keyValue]
|
||||||
|
default:
|
||||||
|
err = collectionTerm.Errorf("collection expected")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAssignValue(ctx ExprContext, leftTerm *term) (value any, err error) {
|
||||||
|
if leftTerm.symbol() == SymIndex {
|
||||||
|
value, err = getCollectionItemValue(ctx, leftTerm.children[0], leftTerm.children[1])
|
||||||
|
} else {
|
||||||
|
value, _ = ctx.GetVar(leftTerm.source())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalOpAssign(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
var rightValue, leftValue any
|
||||||
|
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 rightValue, err = rightChild.compute(ctx); err == nil {
|
||||||
|
if leftValue, err = getAssignValue(ctx, leftTerm); err == nil {
|
||||||
|
switch opTerm.symbol() {
|
||||||
|
case SymPlusEqual:
|
||||||
|
v, err = sumValues(opTerm, leftValue, rightValue)
|
||||||
|
case SymMinusEqual:
|
||||||
|
v, err = diffValues(opTerm, leftValue, rightValue)
|
||||||
|
case SymStarEqual:
|
||||||
|
v, err = mulValues(opTerm, leftValue, rightValue)
|
||||||
|
case SymSlashEqual:
|
||||||
|
v, err = divValues(opTerm, leftValue, rightValue)
|
||||||
|
case SymPercEqual:
|
||||||
|
v, err = remainderValues(opTerm, leftValue, rightValue)
|
||||||
|
case SymAmpersandEqual:
|
||||||
|
v, err = bitwiseAnd(opTerm, leftValue, rightValue)
|
||||||
|
case SymVertBarEqual:
|
||||||
|
v, err = bitwiseOr(opTerm, leftValue, rightValue)
|
||||||
|
case SymCaretEqual:
|
||||||
|
v, err = bitwiseXor(opTerm, leftValue, rightValue)
|
||||||
|
case SymDoubleLessEqual:
|
||||||
|
v, err = bitLeftShift(opTerm, leftValue, rightValue)
|
||||||
|
case SymDoubleGreaterEqual:
|
||||||
|
v, err = bitRightShift(opTerm, leftValue, rightValue)
|
||||||
|
default:
|
||||||
|
err = opTerm.Errorf("unsupported assign operator %q", opTerm.source())
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
err = assignValue(ctx, leftTerm, v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -53,4 +207,14 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
// init
|
// init
|
||||||
func init() {
|
func init() {
|
||||||
registerTermConstructor(SymEqual, newAssignTerm)
|
registerTermConstructor(SymEqual, newAssignTerm)
|
||||||
|
registerTermConstructor(SymPlusEqual, newOpAssignTerm)
|
||||||
|
registerTermConstructor(SymMinusEqual, newOpAssignTerm)
|
||||||
|
registerTermConstructor(SymStarEqual, newOpAssignTerm)
|
||||||
|
registerTermConstructor(SymSlashEqual, newOpAssignTerm)
|
||||||
|
registerTermConstructor(SymPercEqual, newOpAssignTerm)
|
||||||
|
registerTermConstructor(SymDoubleLessEqual, newOpAssignTerm)
|
||||||
|
registerTermConstructor(SymDoubleGreaterEqual, newOpAssignTerm)
|
||||||
|
registerTermConstructor(SymAmpersandEqual, newOpAssignTerm)
|
||||||
|
registerTermConstructor(SymVertBarEqual, newOpAssignTerm)
|
||||||
|
registerTermConstructor(SymCaretEqual, newOpAssignTerm)
|
||||||
}
|
}
|
||||||
|
154
operator-bitwise.go
Normal file
154
operator-bitwise.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-bitwise.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
//-------- Bitwise NOT term
|
||||||
|
|
||||||
|
func newBitwiseNotTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 1),
|
||||||
|
position: posPrefix,
|
||||||
|
priority: priBitwiseNot,
|
||||||
|
evalFunc: evalBitwiseNot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalBitwiseNot(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
var value any
|
||||||
|
|
||||||
|
if value, err = opTerm.evalPrefix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsInteger(value) {
|
||||||
|
i, _ := value.(int64)
|
||||||
|
v = ^i
|
||||||
|
} else {
|
||||||
|
err = opTerm.errIncompatibleType(value)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------- Bitwise AND term
|
||||||
|
|
||||||
|
func newBitwiseAndTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priBitwiseAnd,
|
||||||
|
evalFunc: evalBitwiseAnd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bitwiseAnd(opTerm *term, leftValue, rightValue any) (v any, err error) {
|
||||||
|
var leftInt, rightInt int64
|
||||||
|
var lok, rok bool
|
||||||
|
|
||||||
|
leftInt, lok = leftValue.(int64)
|
||||||
|
rightInt, rok = rightValue.(int64)
|
||||||
|
|
||||||
|
if lok && rok {
|
||||||
|
v = leftInt & rightInt
|
||||||
|
} else {
|
||||||
|
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalBitwiseAnd(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v, err = bitwiseAnd(opTerm, leftValue, rightValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------- Bitwise OR term
|
||||||
|
|
||||||
|
func newBitwiseOrTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priBitwiseOr,
|
||||||
|
evalFunc: evalBitwiseOr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bitwiseOr(opTerm *term, leftValue, rightValue any) (v any, err error) {
|
||||||
|
var leftInt, rightInt int64
|
||||||
|
var lok, rok bool
|
||||||
|
|
||||||
|
leftInt, lok = leftValue.(int64)
|
||||||
|
rightInt, rok = rightValue.(int64)
|
||||||
|
|
||||||
|
if lok && rok {
|
||||||
|
v = leftInt | rightInt
|
||||||
|
} else {
|
||||||
|
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalBitwiseOr(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err = bitwiseOr(opTerm, leftValue, rightValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------- Bitwise XOR term
|
||||||
|
|
||||||
|
func newBitwiseXorTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priBitwiseOr,
|
||||||
|
evalFunc: evalBitwiseXor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bitwiseXor(opTerm *term, leftValue, rightValue any) (v any, err error) {
|
||||||
|
var leftInt, rightInt int64
|
||||||
|
var lok, rok bool
|
||||||
|
|
||||||
|
leftInt, lok = leftValue.(int64)
|
||||||
|
rightInt, rok = rightValue.(int64)
|
||||||
|
|
||||||
|
if lok && rok {
|
||||||
|
v = leftInt ^ rightInt
|
||||||
|
} else {
|
||||||
|
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalBitwiseXor(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err = bitwiseXor(opTerm, leftValue, rightValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymTilde, newBitwiseNotTerm)
|
||||||
|
registerTermConstructor(SymAmpersand, newBitwiseAndTerm)
|
||||||
|
registerTermConstructor(SymVertBar, newBitwiseOrTerm)
|
||||||
|
registerTermConstructor(SymCaret, newBitwiseXorTerm)
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
@ -37,9 +37,7 @@ 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)
|
||||||
@ -106,9 +104,7 @@ 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)
|
||||||
|
@ -18,10 +18,10 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,11 +37,11 @@ func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
if ImportInContext(module) {
|
if ImportInContext(module) {
|
||||||
count++
|
count++
|
||||||
} else {
|
} else {
|
||||||
err = self.Errorf("unknown module %q", module)
|
err = 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
}
|
|
@ -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 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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
124
operator-default.go
Normal file
124
operator-default.go
Normal file
@ -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)
|
||||||
|
}
|
@ -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,80 +15,35 @@ 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 {
|
|
||||||
v = maxValue + v
|
|
||||||
}
|
|
||||||
if v >= 0 && v < maxValue {
|
|
||||||
index = v
|
|
||||||
} else {
|
|
||||||
err = indexTerm.Errorf("index %d out of bounds", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func evalDot(ctx ExprContext, self *term) (v any, err error) {
|
|
||||||
var leftValue, rightValue any
|
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 = string(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, map[string]any{})
|
||||||
} else {
|
} else {
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,10 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
|
@ -7,203 +7,9 @@ package expr
|
|||||||
//https://www.youmath.it/lezioni/algebra-elementare/lezioni-di-algebra-e-aritmetica-per-scuole-medie/553-dalle-frazioni-a-numeri-decimali.html
|
//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"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type fraction struct {
|
|
||||||
num, den int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFraction(num, den int64) *fraction {
|
|
||||||
/* if den < 0 {
|
|
||||||
den = -den
|
|
||||||
num = -num
|
|
||||||
}*/
|
|
||||||
num, den = simplifyIntegers(num, den)
|
|
||||||
return &fraction{num, den}
|
|
||||||
}
|
|
||||||
|
|
||||||
func float64ToFraction(f float64) (fract *fraction, err error) {
|
|
||||||
var sign string
|
|
||||||
intPart, decPart := math.Modf(f)
|
|
||||||
if decPart < 0.0 {
|
|
||||||
sign = "-"
|
|
||||||
intPart = -intPart
|
|
||||||
decPart = -decPart
|
|
||||||
}
|
|
||||||
dec := fmt.Sprintf("%.12f", decPart)
|
|
||||||
s := fmt.Sprintf("%s%.f%s", sign, intPart, dec[1:])
|
|
||||||
// fmt.Printf("S: '%s'\n",s)
|
|
||||||
return makeGeneratingFraction(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Based on https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/math/big/rat.go;l=39
|
|
||||||
/*
|
|
||||||
func _float64ToFraction(f float64) (num, den int64, err error) {
|
|
||||||
const expMask = 1<<11 - 1
|
|
||||||
bits := math.Float64bits(f)
|
|
||||||
mantissa := bits & (1<<52 - 1)
|
|
||||||
exp := int((bits >> 52) & expMask)
|
|
||||||
switch exp {
|
|
||||||
case expMask: // non-finite
|
|
||||||
err = errors.New("infite")
|
|
||||||
return
|
|
||||||
case 0: // denormal
|
|
||||||
exp -= 1022
|
|
||||||
default: // normal
|
|
||||||
mantissa |= 1 << 52
|
|
||||||
exp -= 1023
|
|
||||||
}
|
|
||||||
|
|
||||||
shift := 52 - exp
|
|
||||||
|
|
||||||
// Optimization (?): partially pre-normalise.
|
|
||||||
for mantissa&1 == 0 && shift > 0 {
|
|
||||||
mantissa >>= 1
|
|
||||||
shift--
|
|
||||||
}
|
|
||||||
|
|
||||||
if f < 0 {
|
|
||||||
num = -int64(mantissa)
|
|
||||||
} else {
|
|
||||||
num = int64(mantissa)
|
|
||||||
}
|
|
||||||
den = int64(1)
|
|
||||||
|
|
||||||
if shift > 0 {
|
|
||||||
den = den << shift
|
|
||||||
} else {
|
|
||||||
num = num << (-shift)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func makeGeneratingFraction(s string) (f *fraction, err error) {
|
|
||||||
var num, den int64
|
|
||||||
var sign int64 = 1
|
|
||||||
var parts []string
|
|
||||||
if len(s) == 0 {
|
|
||||||
goto exit
|
|
||||||
}
|
|
||||||
if s[0] == '-' {
|
|
||||||
sign = int64(-1)
|
|
||||||
s = s[1:]
|
|
||||||
} else if s[0] == '+' {
|
|
||||||
s = s[1:]
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(s, "()") {
|
|
||||||
s = s[0 : len(s)-2]
|
|
||||||
}
|
|
||||||
parts = strings.SplitN(s, ".", 2)
|
|
||||||
if num, err = strconv.ParseInt(parts[0], 10, 64); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(parts) == 1 {
|
|
||||||
f = newFraction(sign*num, 1)
|
|
||||||
} else if len(parts) == 2 {
|
|
||||||
subParts := strings.SplitN(parts[1], "(", 2)
|
|
||||||
if len(subParts) == 1 {
|
|
||||||
den = 1
|
|
||||||
dec := parts[1]
|
|
||||||
lsd := len(dec)
|
|
||||||
for i := lsd - 1; i >= 0 && dec[i] == '0'; i-- {
|
|
||||||
lsd--
|
|
||||||
}
|
|
||||||
for _, c := range dec[0:lsd] {
|
|
||||||
if c < '0' || c > '9' {
|
|
||||||
return nil, errExpectedGot("fract", "digit", c)
|
|
||||||
}
|
|
||||||
num = num*10 + int64(c-'0')
|
|
||||||
den = den * 10
|
|
||||||
}
|
|
||||||
f = newFraction(sign*num, den)
|
|
||||||
} else if len(subParts) == 2 {
|
|
||||||
sub := num
|
|
||||||
mul := int64(1)
|
|
||||||
for _, c := range subParts[0] {
|
|
||||||
if c < '0' || c > '9' {
|
|
||||||
return nil, errExpectedGot("fract", "digit", c)
|
|
||||||
}
|
|
||||||
num = num*10 + int64(c-'0')
|
|
||||||
sub = sub*10 + int64(c-'0')
|
|
||||||
mul *= 10
|
|
||||||
}
|
|
||||||
if len(subParts) == 2 {
|
|
||||||
if s[len(s)-1] != ')' {
|
|
||||||
goto exit
|
|
||||||
}
|
|
||||||
p := subParts[1][0 : len(subParts[1])-1]
|
|
||||||
for _, c := range p {
|
|
||||||
if c < '0' || c > '9' {
|
|
||||||
return nil, errExpectedGot("fract", "digit", c)
|
|
||||||
}
|
|
||||||
num = num*10 + int64(c-'0')
|
|
||||||
den = den*10 + 9
|
|
||||||
}
|
|
||||||
den *= mul
|
|
||||||
}
|
|
||||||
num -= sub
|
|
||||||
f = newFraction(sign*num, den)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exit:
|
|
||||||
if f == nil {
|
|
||||||
err = errors.New("bad syntax")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fraction) toFloat() float64 {
|
|
||||||
return float64(f.num) / float64(f.den)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fraction) String() string {
|
|
||||||
return f.ToString(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fraction) ToString(opt FmtOpt) string {
|
|
||||||
var sb strings.Builder
|
|
||||||
if opt&MultiLine == 0 {
|
|
||||||
sb.WriteString(fmt.Sprintf("%d|%d", f.num, f.den))
|
|
||||||
} else {
|
|
||||||
var s, num string
|
|
||||||
if f.num < 0 && opt&TTY == 0 {
|
|
||||||
num = strconv.FormatInt(-f.num, 10)
|
|
||||||
s = "-"
|
|
||||||
} else {
|
|
||||||
num = strconv.FormatInt(f.num, 10)
|
|
||||||
}
|
|
||||||
den := strconv.FormatInt(f.den, 10)
|
|
||||||
size := max(len(num), len(den))
|
|
||||||
if opt&TTY != 0 {
|
|
||||||
sb.WriteString(fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, s+num)))
|
|
||||||
} else {
|
|
||||||
if len(s) > 0 {
|
|
||||||
sb.WriteString(" ")
|
|
||||||
}
|
|
||||||
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, num)))
|
|
||||||
sb.WriteByte('\n')
|
|
||||||
if len(s) > 0 {
|
|
||||||
sb.WriteString(s)
|
|
||||||
sb.WriteByte(' ')
|
|
||||||
}
|
|
||||||
sb.WriteString(strings.Repeat("-", size))
|
|
||||||
sb.WriteByte('\n')
|
|
||||||
if len(s) > 0 {
|
|
||||||
sb.WriteString(" ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------- fraction term
|
// -------- fraction term
|
||||||
func newFractionTerm(tk *Token) *term {
|
func newFractionTerm(tk *Token) *term {
|
||||||
return &term{
|
return &term{
|
||||||
@ -217,12 +23,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 {
|
||||||
@ -234,7 +40,7 @@ func evalFraction(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if den == 0 {
|
if den == 0 {
|
||||||
err = errors.New("division by zero")
|
err = opTerm.errDivisionByZero()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,175 +48,23 @@ func evalFraction(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
den = -den
|
den = -den
|
||||||
num = -num
|
num = -num
|
||||||
}
|
}
|
||||||
g := gcd(num, den)
|
if num != 0 {
|
||||||
num = num / g
|
g := gcd(num, den)
|
||||||
den = den / g
|
num = num / g
|
||||||
if den == 1 {
|
den = den / g
|
||||||
v = num
|
if den == 1 {
|
||||||
} else {
|
v = num
|
||||||
v = &fraction{num, den}
|
} else {
|
||||||
}
|
v = &FractionType{num, den}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func gcd(a, b int64) (g int64) {
|
|
||||||
if a < 0 {
|
|
||||||
a = -a
|
|
||||||
}
|
|
||||||
if b < 0 {
|
|
||||||
b = -b
|
|
||||||
}
|
|
||||||
if a < b {
|
|
||||||
a, b = b, a
|
|
||||||
}
|
|
||||||
r := a % b
|
|
||||||
for r > 0 {
|
|
||||||
a, b = b, r
|
|
||||||
r = a % b
|
|
||||||
}
|
|
||||||
g = b
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func lcm(a, b int64) (l int64) {
|
|
||||||
g := gcd(a, b)
|
|
||||||
l = a * b / g
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func sumFract(f1, f2 *fraction) (sum *fraction) {
|
|
||||||
m := lcm(f1.den, f2.den)
|
|
||||||
sum = newFraction(f1.num*(m/f1.den)+f2.num*(m/f2.den), m)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func mulFract(f1, f2 *fraction) (prod *fraction) {
|
|
||||||
prod = newFraction(f1.num*f2.num, f1.den*f2.den)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func anyToFract(v any) (f *fraction, err error) {
|
|
||||||
var ok bool
|
|
||||||
if f, ok = v.(*fraction); !ok {
|
|
||||||
if n, ok := v.(int64); ok {
|
|
||||||
f = intToFraction(n)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if f == nil {
|
|
||||||
err = errExpectedGot("fract", typeFraction, v)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func anyPairToFract(v1, v2 any) (f1, f2 *fraction, err error) {
|
|
||||||
if f1, err = anyToFract(v1); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if f2, err = anyToFract(v2); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func sumAnyFract(af1, af2 any) (sum any, err error) {
|
|
||||||
var f1, f2 *fraction
|
|
||||||
if f1, f2, err = anyPairToFract(af1, af2); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f := sumFract(f1, f2)
|
|
||||||
if f.num == 0 {
|
|
||||||
sum = 0
|
|
||||||
} else {
|
} else {
|
||||||
sum = simplifyFraction(f)
|
v = &FractionType{0, den}
|
||||||
}
|
}
|
||||||
return
|
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)
|
||||||
|
registerTermConstructor(SymColon, newFractionTerm)
|
||||||
}
|
}
|
||||||
|
@ -16,26 +16,26 @@ func newInTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasKey(d map[any]any, target any) (ok bool) {
|
// func hasKey(d map[any]any, target any) (ok bool) {
|
||||||
_, ok = d[target]
|
// _, ok = d[target]
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
func evalIn(ctx ExprContext, self *term) (v any, err error) {
|
func evalIn(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)
|
||||||
v = list.indexDeepCmp(leftValue) >= 0
|
v = list.indexDeepSameCmp(leftValue) >= 0
|
||||||
} else if IsDict(rightValue) {
|
} else if IsDict(rightValue) {
|
||||||
d, _ := rightValue.(map[any]any)
|
dict, _ := rightValue.(*DictType)
|
||||||
v = hasKey(d, leftValue)
|
v = dict.hasKey(leftValue)
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
count++
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleType(childValue)
|
err = opTerm.errIncompatibleType(childValue)
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err != nil {
|
||||||
v = count
|
//v = count
|
||||||
|
v = nil
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
138
operator-index.go
Normal file
138
operator-index.go
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
// 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)
|
||||||
|
} else {
|
||||||
|
rightChild := opTerm.children[1]
|
||||||
|
err = rightChild.Errorf("invalid index type: %v", (*indexList)[0])
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
@ -4,15 +4,15 @@
|
|||||||
// operator-insert.go
|
// operator-insert.go
|
||||||
package expr
|
package expr
|
||||||
|
|
||||||
//-------- insert term
|
//-------- prepend term
|
||||||
|
|
||||||
func newInsertTerm(tk *Token) (inst *term) {
|
func newPrependTerm(tk *Token) (inst *term) {
|
||||||
return &term{
|
return &term{
|
||||||
tk: *tk,
|
tk: *tk,
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priAssign,
|
priority: priInsert,
|
||||||
evalFunc: evalInsert,
|
evalFunc: evalPrepend,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,15 +21,15 @@ func newAppendTerm(tk *Token) (inst *term) {
|
|||||||
tk: *tk,
|
tk: *tk,
|
||||||
children: make([]*term, 0, 2),
|
children: make([]*term, 0, 2),
|
||||||
position: posInfix,
|
position: posInfix,
|
||||||
priority: priAssign,
|
priority: priInsert,
|
||||||
evalFunc: evalAppend,
|
evalFunc: evalAppend,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalInsert(ctx ExprContext, self *term) (v any, err error) {
|
func evalPrepend(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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,16 +37,19 @@ func evalInsert(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,14 +57,35 @@ func evalAppend(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
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(SymPlusGreater, newPrependTerm)
|
||||||
registerTermConstructor(SymAppend, newAppendTerm)
|
registerTermConstructor(SymLessPlus, newAppendTerm)
|
||||||
}
|
}
|
||||||
|
@ -11,27 +11,28 @@ func newIterValueTerm(tk *Token) (inst *term) {
|
|||||||
tk: *tk,
|
tk: *tk,
|
||||||
children: make([]*term, 0, 1),
|
children: make([]*term, 0, 1),
|
||||||
position: posPrefix,
|
position: posPrefix,
|
||||||
priority: priIterValue,
|
priority: priDereference,
|
||||||
evalFunc: evalIterValue,
|
evalFunc: evalIterValue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(SymDereference, newIterValueTerm)
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,10 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,17 +30,18 @@ func evalLength(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
s, _ := childValue.(string)
|
s, _ := childValue.(string)
|
||||||
v = int64(len(s))
|
v = int64(len(s))
|
||||||
} else if IsDict(childValue) {
|
} else if IsDict(childValue) {
|
||||||
m, _ := childValue.(map[any]any)
|
m, _ := childValue.(*DictType)
|
||||||
v = int64(len(m))
|
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) {
|
v = int64(it.Count())
|
||||||
count, _ := extIt.CallOperation(countName, nil)
|
// if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(CountName) {
|
||||||
v, _ = toInt(count, "")
|
// count, _ := extIt.CallOperation(CountName, nil)
|
||||||
} else {
|
// v, _ = ToGoInt(count, "")
|
||||||
v = int64(it.Index() + 1)
|
// } else {
|
||||||
}
|
// v = int64(it.Index() + 1)
|
||||||
|
// }
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleType(childValue)
|
err = opTerm.errIncompatibleType(childValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
36
operator-plugin.go
Normal file
36
operator-plugin.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-plugin.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
//-------- 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 count int
|
||||||
|
|
||||||
|
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if count, err = importPluginFromSearchPath(childValue); err == nil {
|
||||||
|
v = int64(count)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymKwPlugin, newPluginTerm)
|
||||||
|
}
|
73
operator-post-inc-dec.go
Normal file
73
operator-post-inc-dec.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-post-inc-dec.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
// -------- post increment term
|
||||||
|
|
||||||
|
func newPostIncTerm(tk *Token) *term {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
parent: nil,
|
||||||
|
children: make([]*term, 0, 1),
|
||||||
|
position: posPostfix,
|
||||||
|
priority: priIncDec,
|
||||||
|
evalFunc: evalPostInc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalPostInc(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
var childValue any
|
||||||
|
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if it, ok := childValue.(Iterator); ok {
|
||||||
|
v, err = it.Next()
|
||||||
|
} else if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
|
||||||
|
v = childValue
|
||||||
|
i, _ := childValue.(int64)
|
||||||
|
ctx.SetVar(opTerm.children[0].source(), i+1)
|
||||||
|
} else {
|
||||||
|
err = opTerm.errIncompatibleType(childValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- post decrement term
|
||||||
|
|
||||||
|
func newPostDecTerm(tk *Token) *term {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
parent: nil,
|
||||||
|
children: make([]*term, 0, 1),
|
||||||
|
position: posPostfix,
|
||||||
|
priority: priIncDec,
|
||||||
|
evalFunc: evalPostDec,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalPostDec(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
var childValue any
|
||||||
|
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if it, ok := childValue.(Iterator); ok {
|
||||||
|
v, err = it.Next()
|
||||||
|
} else */if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
|
||||||
|
v = childValue
|
||||||
|
i, _ := childValue.(int64)
|
||||||
|
ctx.SetVar(opTerm.children[0].source(), i-1)
|
||||||
|
} else {
|
||||||
|
err = opTerm.errIncompatibleType(childValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymDoublePlus, newPostIncTerm)
|
||||||
|
registerTermConstructor(SymDoubleMinus, newPostDecTerm)
|
||||||
|
}
|
@ -1,41 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// operator-post-inc.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
// -------- post increment term
|
|
||||||
|
|
||||||
func newPostIncTerm(tk *Token) *term {
|
|
||||||
return &term{
|
|
||||||
tk: *tk,
|
|
||||||
parent: nil,
|
|
||||||
children: make([]*term, 0, 1),
|
|
||||||
position: posPostfix,
|
|
||||||
priority: priIncDec,
|
|
||||||
evalFunc: evalPostInc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
|
|
||||||
var childValue any
|
|
||||||
if childValue, err = self.evalPrefix(ctx); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if it, ok := childValue.(Iterator); ok {
|
|
||||||
v, err = it.Next()
|
|
||||||
} else if IsInteger(childValue) && self.children[0].symbol() == SymIdentifier {
|
|
||||||
v = childValue
|
|
||||||
i, _ := childValue.(int64)
|
|
||||||
ctx.SetVar(self.children[0].source(), i+1)
|
|
||||||
} else {
|
|
||||||
err = self.errIncompatibleType(childValue)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// init
|
|
||||||
func init() {
|
|
||||||
registerTermConstructor(SymDoublePlus, newPostIncTerm)
|
|
||||||
}
|
|
69
operator-pre-inc-dec.go
Normal file
69
operator-pre-inc-dec.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-pre-inc-dec.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
// -------- pre increment term
|
||||||
|
|
||||||
|
func newPreIncTerm(tk *Token) *term {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
parent: nil,
|
||||||
|
children: make([]*term, 0, 1),
|
||||||
|
position: posPrefix,
|
||||||
|
priority: priIncDec,
|
||||||
|
evalFunc: evalPreInc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalPreInc(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
var childValue any
|
||||||
|
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
|
||||||
|
i := childValue.(int64) + 1
|
||||||
|
ctx.SetVar(opTerm.children[0].source(), i)
|
||||||
|
v = i
|
||||||
|
} else {
|
||||||
|
err = opTerm.errIncompatibleType(childValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- pre decrement term
|
||||||
|
|
||||||
|
func newPreDecTerm(tk *Token) *term {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
parent: nil,
|
||||||
|
children: make([]*term, 0, 1),
|
||||||
|
position: posPrefix,
|
||||||
|
priority: priIncDec,
|
||||||
|
evalFunc: evalPreDec,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalPreDec(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
var childValue any
|
||||||
|
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
|
||||||
|
i := childValue.(int64) - 1
|
||||||
|
ctx.SetVar(opTerm.children[0].source(), i)
|
||||||
|
v = i
|
||||||
|
} else {
|
||||||
|
err = opTerm.errIncompatibleType(childValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymPreInc, newPreIncTerm)
|
||||||
|
registerTermConstructor(SymPreDec, newPreDecTerm)
|
||||||
|
}
|
119
operator-prod.go
119
operator-prod.go
@ -5,7 +5,6 @@
|
|||||||
package expr
|
package expr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -13,9 +12,7 @@ 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,13 +20,7 @@ func newMultiplyTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalMultiply(ctx ExprContext, self *term) (v any, err error) {
|
func mulValues(opTerm *term, leftValue, rightValue any) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
|
||||||
|
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if IsString(leftValue) && IsInteger(rightValue) {
|
if IsString(leftValue) && IsInteger(rightValue) {
|
||||||
s, _ := leftValue.(string)
|
s, _ := leftValue.(string)
|
||||||
n, _ := rightValue.(int64)
|
n, _ := rightValue.(int64)
|
||||||
@ -45,18 +36,26 @@ func evalMultiply(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
v = leftInt * rightInt
|
v = leftInt * rightInt
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func evalMultiply(ctx ExprContext, prodTerm *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = prodTerm.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return mulValues(prodTerm, leftValue, rightValue)
|
||||||
|
}
|
||||||
|
|
||||||
//-------- divide term
|
//-------- divide term
|
||||||
|
|
||||||
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,18 +63,12 @@ func newDivideTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalDivide(ctx ExprContext, self *term) (v any, err error) {
|
func divValues(opTerm *term, leftValue, rightValue any) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
|
||||||
|
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if isNumOrFract(leftValue) && isNumOrFract(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 = opTerm.errDivisionByZero()
|
||||||
} else {
|
} else {
|
||||||
v = numAsFloat(leftValue) / d
|
v = numAsFloat(leftValue) / d
|
||||||
}
|
}
|
||||||
@ -84,17 +77,52 @@ func evalDivide(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
} else {
|
} else {
|
||||||
leftInt, _ := leftValue.(int64)
|
leftInt, _ := leftValue.(int64)
|
||||||
if rightInt, _ := rightValue.(int64); rightInt == 0 {
|
if rightInt, _ := rightValue.(int64); rightInt == 0 {
|
||||||
err = errors.New("division by zero")
|
err = opTerm.errDivisionByZero()
|
||||||
} else {
|
} else {
|
||||||
v = leftInt / rightInt
|
v = leftInt / rightInt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if IsString(leftValue) && IsString(rightValue) {
|
||||||
|
source := leftValue.(string)
|
||||||
|
sep := rightValue.(string)
|
||||||
|
v = ListFromStrings(strings.Split(source, sep))
|
||||||
|
} else if IsString(leftValue) && IsInteger(rightValue) {
|
||||||
|
source := leftValue.(string)
|
||||||
|
partSize := int(rightValue.(int64))
|
||||||
|
if partSize == 0 {
|
||||||
|
err = opTerm.errDivisionByZero()
|
||||||
|
} else {
|
||||||
|
partCount := len(source) / partSize
|
||||||
|
remainder := len(source) % partSize
|
||||||
|
listSize := partCount
|
||||||
|
if remainder > 0 {
|
||||||
|
listSize++
|
||||||
|
}
|
||||||
|
parts := make([]any, 0, listSize)
|
||||||
|
for i := 0; i < partCount; i++ {
|
||||||
|
parts = append(parts, source[i*partSize:(i+1)*partSize])
|
||||||
|
}
|
||||||
|
if remainder > 0 {
|
||||||
|
parts = append(parts, source[len(source)-remainder:])
|
||||||
|
}
|
||||||
|
v = newList(parts)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func evalDivide(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return divValues(opTerm, leftValue, rightValue)
|
||||||
|
}
|
||||||
|
|
||||||
//-------- divide as float term
|
//-------- divide as float term
|
||||||
|
|
||||||
func newDivideAsFloatTerm(tk *Token) (inst *term) {
|
func newDivideAsFloatTerm(tk *Token) (inst *term) {
|
||||||
@ -107,65 +135,66 @@ 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 isNumOrFract(leftValue) && isNumOrFract(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 = floatDivTerm.errDivisionByZero()
|
||||||
} else {
|
} else {
|
||||||
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,
|
||||||
evalFunc: evalReminder,
|
evalFunc: evalRemainder,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func remainderValues(opTerm *term, leftValue, rightValue any) (v any, err error) {
|
||||||
func evalReminder(ctx ExprContext, self *term) (v any, err error) {
|
|
||||||
var leftValue, rightValue any
|
|
||||||
|
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
|
||||||
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 = opTerm.errDivisionByZero()
|
||||||
} else {
|
} else {
|
||||||
leftInt, _ := leftValue.(int64)
|
leftInt, _ := leftValue.(int64)
|
||||||
v = leftInt % rightInt
|
v = leftInt % rightInt
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func evalRemainder(ctx ExprContext, remainderTerm *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = remainderTerm.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return remainderValues(remainderTerm, leftValue, rightValue)
|
||||||
|
}
|
||||||
|
|
||||||
// init
|
// init
|
||||||
func init() {
|
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)
|
||||||
}
|
}
|
||||||
|
82
operator-range.go
Normal file
82
operator-range.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
// 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 changeColonToRange(t *term) {
|
||||||
|
if t.tk.IsSymbol(SymColon) {
|
||||||
|
t.tk.Sym = SymRange
|
||||||
|
t.evalFunc = evalRange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalRange(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
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)
|
||||||
|
err = errRangeInvalidSpecification(opTerm)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
startIndex, _ := leftValue.(int64)
|
||||||
|
endIndex, _ := rightValue.(int64)
|
||||||
|
|
||||||
|
v = &intPair{int(startIndex), int(endIndex)}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func errRangeInvalidSpecification(t *term) error {
|
||||||
|
return t.Errorf("invalid range specification")
|
||||||
|
}
|
||||||
|
|
||||||
|
func errRangeUnexpectedExpression(t *term) error {
|
||||||
|
return t.Errorf("unexpected range expression")
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymRange, newRangeTerm)
|
||||||
|
}
|
208
operator-rel.go
208
operator-rel.go
@ -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)
|
||||||
|
|
||||||
|
func equals(a, b any, deepCmp deepFuncTemplate) (eq bool, err error) {
|
||||||
|
if isNumOrFract(a) && isNumOrFract(b) {
|
||||||
|
if IsNumber(a) && IsNumber(b) {
|
||||||
|
if IsInteger(a) && IsInteger(b) {
|
||||||
|
li, _ := a.(int64)
|
||||||
|
ri, _ := b.(int64)
|
||||||
|
eq = li == ri
|
||||||
|
} else {
|
||||||
|
eq = numAsFloat(a) == numAsFloat(b)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var cmp int
|
||||||
|
if cmp, err = cmpAnyFract(a, b); err == nil {
|
||||||
|
eq = cmp == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if deepCmp != nil && IsList(a) && IsList(b) {
|
||||||
|
eq, err = deepCmp(a, b)
|
||||||
|
} else {
|
||||||
|
eq = reflect.DeepEqual(a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalEqual(ctx ExprContext, 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) {
|
v, err = equals(leftValue, rightValue, nil)
|
||||||
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,9 +60,7 @@ 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) {
|
||||||
|
if isNumOrFract(a) && isNumOrFract(b) {
|
||||||
|
if IsNumber(a) && IsNumber(b) {
|
||||||
|
if IsInteger(a) && IsInteger(b) {
|
||||||
|
li, _ := a.(int64)
|
||||||
|
ri, _ := b.(int64)
|
||||||
|
isLess = li < ri
|
||||||
|
} else {
|
||||||
|
isLess = numAsFloat(a) < numAsFloat(b)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var cmp int
|
||||||
|
if cmp, err = cmpAnyFract(a, b); err == nil {
|
||||||
|
isLess = cmp < 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if IsString(a) && IsString(b) {
|
||||||
|
ls, _ := a.(string)
|
||||||
|
rs, _ := b.(string)
|
||||||
|
isLess = ls < rs
|
||||||
|
// Inclusion test
|
||||||
|
} else if IsList(a) && IsList(b) {
|
||||||
|
aList, _ := a.(*ListType)
|
||||||
|
bList, _ := b.(*ListType)
|
||||||
|
isLess = len(*aList) < len(*bList) && bList.contains(aList)
|
||||||
|
} else {
|
||||||
|
err = self.errIncompatibleTypes(a, b)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalLess(ctx ExprContext, 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
|
||||||
}
|
}
|
||||||
|
v, err = lessThan(opTerm, leftValue, rightValue)
|
||||||
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
|
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) {
|
||||||
|
if isLessEq, err = lessThan(self, a, b); err == nil {
|
||||||
|
if !isLessEq {
|
||||||
|
if IsList(a) && IsList(b) {
|
||||||
|
isLessEq, err = sameContent(a, b)
|
||||||
|
} else {
|
||||||
|
isLessEq, err = equals(a, b, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalLessEqual(ctx ExprContext, 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) {
|
v, err = lessThanOrEqual(opTerm, leftValue, 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,9 +170,7 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,9 +193,7 @@ 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,27 @@ 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 {
|
||||||
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
var valueAsInt = int64(0)
|
||||||
match = true
|
if b, ok := exprValue.(bool); ok {
|
||||||
|
if !b {
|
||||||
|
valueAsInt = 1
|
||||||
|
}
|
||||||
|
} else if valueAsInt, ok = exprValue.(int64); !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if valueAsInt == int64(caseIndex) {
|
||||||
|
selectedValue, err = caseData.caseExpr.Eval(ctx)
|
||||||
|
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 +49,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()
|
||||||
|
77
operator-shift.go
Normal file
77
operator-shift.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-shift.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
//-------- bit right shift term
|
||||||
|
|
||||||
|
func newRightShiftTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priBinShift,
|
||||||
|
evalFunc: evalRightShift,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bitRightShift(opTerm *term, leftValue, rightValue any) (v any, err error) {
|
||||||
|
if IsInteger(leftValue) && IsInteger(rightValue) {
|
||||||
|
leftInt := leftValue.(int64)
|
||||||
|
rightInt := rightValue.(int64)
|
||||||
|
v = leftInt >> rightInt
|
||||||
|
} else {
|
||||||
|
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalRightShift(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err = bitRightShift(opTerm, leftValue, rightValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLeftShiftTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priBinShift,
|
||||||
|
evalFunc: evalLeftShift,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bitLeftShift(opTerm *term, leftValue, rightValue any) (v any, err error) {
|
||||||
|
if IsInteger(leftValue) && IsInteger(rightValue) {
|
||||||
|
leftInt := leftValue.(int64)
|
||||||
|
rightInt := rightValue.(int64)
|
||||||
|
v = leftInt << rightInt
|
||||||
|
} else {
|
||||||
|
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalLeftShift(ctx ExprContext, opTerm *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err = bitLeftShift(opTerm, leftValue, rightValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymDoubleGreater, newRightShiftTerm)
|
||||||
|
registerTermConstructor(SymDoubleLess, newLeftShiftTerm)
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -21,13 +21,7 @@ func newPlusTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalPlus(ctx ExprContext, self *term) (v any, err error) {
|
func sumValues(plusTerm *term, leftValue, rightValue any) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
|
||||||
|
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
|
||||||
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) {
|
||||||
@ -38,41 +32,43 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
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)) {
|
||||||
if IsFloat(leftValue) || IsFloat(rightValue) {
|
if IsFloat(leftValue) || IsFloat(rightValue) {
|
||||||
v = numAsFloat(leftValue) + numAsFloat(rightValue)
|
v = numAsFloat(leftValue) + numAsFloat(rightValue)
|
||||||
} else {
|
} else {
|
||||||
v, err = sumAnyFract(leftValue, rightValue)
|
v, err = sumAnyFract(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
} else if IsDict(leftValue) && IsDict(rightValue) {
|
} else if IsDict(leftValue) && IsDict(rightValue) {
|
||||||
leftDict, _ := leftValue.(map[any]any)
|
leftDict, _ := leftValue.(*DictType)
|
||||||
rightDict, _ := rightValue.(map[any]any)
|
rightDict, _ := rightValue.(*DictType)
|
||||||
c := CloneMap(leftDict)
|
c := leftDict.clone()
|
||||||
for key, value := range rightDict {
|
c.merge(rightDict)
|
||||||
c[key] = value
|
|
||||||
}
|
|
||||||
v = c
|
v = c
|
||||||
|
} else if isFraction(leftValue) && isFraction(rightValue) {
|
||||||
|
v, err = sumAnyFract(leftValue, rightValue)
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = plusTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
return
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalPlus(ctx ExprContext, plusTerm *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = plusTerm.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return sumValues(plusTerm, leftValue, rightValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------- minus term
|
//-------- minus term
|
||||||
@ -87,13 +83,7 @@ func newMinusTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalMinus(ctx ExprContext, self *term) (v any, err error) {
|
func diffValues(minusTerm *term, leftValue, rightValue any) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
|
||||||
|
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if isNumOrFract(leftValue) && isNumOrFract(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)
|
||||||
@ -115,11 +105,21 @@ func evalMinus(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
}
|
}
|
||||||
v = &diffList
|
v = &diffList
|
||||||
} else {
|
} else {
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = minusTerm.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func evalMinus(ctx ExprContext, minusTerm *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
if leftValue, rightValue, err = minusTerm.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return diffValues(minusTerm, leftValue, rightValue)
|
||||||
|
}
|
||||||
|
|
||||||
// init
|
// init
|
||||||
func init() {
|
func init() {
|
||||||
registerTermConstructor(SymPlus, newPlusTerm)
|
registerTermConstructor(SymPlus, newPlusTerm)
|
||||||
|
69
operator-unset.go
Normal file
69
operator-unset.go
Normal file
@ -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)
|
||||||
|
}
|
434
parser.go
434
parser.go
@ -6,51 +6,83 @@ package expr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
)
|
)
|
||||||
|
|
||||||
//-------- parser
|
//-------- parser
|
||||||
|
|
||||||
type parser struct {
|
type parserContext uint16
|
||||||
ctx ExprContext
|
|
||||||
|
const (
|
||||||
|
parserNoFlags = 0
|
||||||
|
allowMultiExpr parserContext = 1 << iota
|
||||||
|
allowVarRef
|
||||||
|
selectorContext
|
||||||
|
listContext // squareContext for list
|
||||||
|
indexContext // squareContext for index
|
||||||
|
allowIndex // allow index in squareContext
|
||||||
|
squareContext = listContext | indexContext // Square parenthesis for list or index
|
||||||
|
)
|
||||||
|
|
||||||
|
func hasFlag[T constraints.Unsigned](set T, singleFlag T) bool {
|
||||||
|
return (set & singleFlag) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewParser(ctx ExprContext) (p *parser) {
|
func addFlags[T constraints.Unsigned](set T, flags T) T {
|
||||||
p = &parser{
|
return set | flags
|
||||||
ctx: ctx,
|
}
|
||||||
|
|
||||||
|
func addFlagsCond[T constraints.Unsigned](set T, flags T, cond bool) (newSet T) {
|
||||||
|
if cond {
|
||||||
|
newSet = set | flags
|
||||||
|
} else {
|
||||||
|
newSet = set
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func remFlags[T constraints.Unsigned](set T, flags T) T {
|
||||||
|
return set & (^flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
type parser struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewParser() (p *parser) {
|
||||||
|
p = &parser{}
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token) (tree *term, err error) {
|
func (parser *parser) Next(scanner *scanner) (tk *Token) {
|
||||||
// name, _ := tk.Value.(string)
|
for tk = scanner.Next(); tk.IsSymbol(SymComment); tk = scanner.Next() {
|
||||||
// funcObj := self.ctx.GetFuncInfo(name)
|
}
|
||||||
// if funcObj == nil {
|
return
|
||||||
// err = fmt.Errorf("unknown function %s()", name)
|
}
|
||||||
// return
|
|
||||||
// }
|
func (parser *parser) parseFuncCall(scanner *scanner, ctx parserContext, tk *Token) (tree *term, err error) {
|
||||||
// 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, ctx, 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 +90,40 @@ 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 = parser.Next(scanner)
|
||||||
if tk.IsSymbol(SymIdentifier) {
|
if tk.IsSymbol(SymIdentifier) {
|
||||||
param := newTerm(tk)
|
param := newTerm(tk)
|
||||||
|
if len(args) > 0 {
|
||||||
|
if pos := paramAlreadyDefined(args, param); pos > 0 {
|
||||||
|
err = tk.Errorf("parameter %q at position %d already defined at position %d", param.source(), len(args)+1, pos)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
args = append(args, param)
|
args = append(args, param)
|
||||||
tk = scanner.Next()
|
tk = parser.Next(scanner)
|
||||||
|
if tk.Sym == SymEqual {
|
||||||
|
var paramExpr *ast
|
||||||
|
defaultParamsStarted = true
|
||||||
|
if paramExpr, err = parser.parseItem(scanner, parserNoFlags, 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
|
||||||
@ -135,15 +134,14 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
|||||||
err = tk.ErrorExpectedGot(")")
|
err = tk.ErrorExpectedGot(")")
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
tk = scanner.Next()
|
tk = parser.Next(scanner)
|
||||||
if tk.IsSymbol(SymOpenBrace) {
|
if tk.IsSymbol(SymOpenBrace) {
|
||||||
body, err = self.parseGeneral(scanner, true, true, SymClosedBrace)
|
body, err = parser.parseGeneral(scanner, allowMultiExpr|allowVarRef, 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 +152,50 @@ 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 paramAlreadyDefined(args []*term, param *term) (position int) {
|
||||||
|
position = 0
|
||||||
|
for i, arg := range args {
|
||||||
|
if arg.source() == param.source() {
|
||||||
|
position = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (parser *parser) parseList(scanner *scanner, ctx parserContext) (listTerm *term, err error) {
|
||||||
|
r, c := scanner.lastPos()
|
||||||
args := make([]*term, 0)
|
args := make([]*term, 0)
|
||||||
lastSym := SymUnknown
|
lastSym := SymUnknown
|
||||||
itemExpected := false
|
itemExpected := false
|
||||||
|
itemCtx := remFlags(ctx, allowIndex)
|
||||||
for lastSym != SymClosedSquare && lastSym != SymEos {
|
for lastSym != SymClosedSquare && lastSym != SymEos {
|
||||||
var subTree *ast
|
zeroRequired := scanner.current.Sym == SymColon
|
||||||
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
|
var itemTree *ast
|
||||||
if subTree.root != nil {
|
if itemTree, err = parser.parseItem(scanner, itemCtx, SymComma, SymClosedSquare); err == nil {
|
||||||
args = append(args, subTree.root)
|
root := itemTree.root
|
||||||
|
if root != nil {
|
||||||
|
if hasFlag(ctx, allowIndex) && root.symbol() == SymColon {
|
||||||
|
changeColonToRange(root)
|
||||||
|
}
|
||||||
|
if !hasFlag(ctx, allowIndex) && root.symbol() == SymRange {
|
||||||
|
// err = root.Errorf("unexpected range expression")
|
||||||
|
err = errRangeUnexpectedExpression(root)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
args = append(args, root)
|
||||||
|
if hasFlag(ctx, allowIndex) && root.symbol() == SymRange && 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")
|
||||||
|
err = errRangeInvalidSpecification(root)
|
||||||
|
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")
|
||||||
@ -172,27 +205,28 @@ func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
lastSym = scanner.Previous().Sym
|
lastSym = scanner.Previous().Sym
|
||||||
itemExpected = lastSym == SymComma
|
if itemExpected = lastSym == SymComma; itemExpected {
|
||||||
|
remFlags(ctx, allowIndex)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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)
|
listTerm = newListTerm(r, c, args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
func (parser *parser) parseIterDef(scanner *scanner, ctx parserContext) (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, ctx, 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 +241,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,8 +250,8 @@ 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) (key any, err error) {
|
||||||
tk := scanner.Next()
|
tk := parser.Next(scanner)
|
||||||
if tk.Sym == SymError {
|
if tk.Sym == SymError {
|
||||||
err = tk.Error()
|
err = tk.Error()
|
||||||
return
|
return
|
||||||
@ -227,27 +260,26 @@ func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if tk.Sym == SymInteger || tk.Sym == SymString {
|
if tk.Sym == SymInteger || tk.Sym == SymString {
|
||||||
tkSep := scanner.Next()
|
tkSep := parser.Next(scanner)
|
||||||
if tkSep.Sym != SymColon {
|
if tkSep.Sym != SymColon {
|
||||||
err = tkSep.ErrorExpectedGot(":")
|
err = tkSep.ErrorExpectedGot(":")
|
||||||
} else {
|
} else {
|
||||||
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, ctx parserContext) (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); err != nil {
|
||||||
break
|
break
|
||||||
} else if key == nil {
|
} else if key == nil {
|
||||||
tk := scanner.Previous()
|
tk := scanner.Previous()
|
||||||
@ -257,10 +289,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, ctx, 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,7 +304,6 @@ 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 {
|
||||||
@ -282,10 +313,11 @@ func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaultCase bool) (caseTerm *term, err error) {
|
func (parser *parser) parseSelectorCase(scanner *scanner, ctx parserContext, defaultCase bool) (caseTerm *term, err error) {
|
||||||
var filterList *term
|
var filterList *term
|
||||||
var caseExpr *ast
|
var caseExpr *ast
|
||||||
tk := scanner.Next()
|
ctx = remFlags(ctx, allowIndex)
|
||||||
|
tk := parser.Next(scanner)
|
||||||
startRow := tk.row
|
startRow := tk.row
|
||||||
startCol := tk.col
|
startCol := tk.col
|
||||||
if tk.Sym == SymOpenSquare {
|
if tk.Sym == SymOpenSquare {
|
||||||
@ -293,18 +325,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, remFlags(ctx, allowIndex)); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tk = scanner.Next()
|
tk = parser.Next(scanner)
|
||||||
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, ctx|allowMultiExpr, SymClosedBrace); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -330,43 +362,89 @@ 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, ctx parserContext) (selectorTerm *term, err error) {
|
||||||
var caseTerm *term
|
var caseTerm *term
|
||||||
|
|
||||||
|
ctx = remFlags(ctx, allowIndex)
|
||||||
tk := scanner.makeToken(SymSelector, '?')
|
tk := scanner.makeToken(SymSelector, '?')
|
||||||
if selectorTerm, err = tree.addToken2(tk); err != nil {
|
if selectorTerm, err = tree.addToken(tk); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, false); err == nil {
|
if caseTerm, err = parser.parseSelectorCase(scanner, ctx|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, ctx parserContext, termSymbols ...Symbol) (tree *ast, err error) {
|
||||||
return self.parseGeneral(scanner, false, allowVarRef, termSymbols...)
|
return parser.parseGeneral(scanner, ctx|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...)
|
termSymbols = append(termSymbols, SymEos)
|
||||||
|
return parser.parseGeneral(scanner, allowMultiExpr, 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 listSubTree(tree *ast, listTerm *term, allowIndeces bool) (root *term, err error) {
|
||||||
|
var tk *Token
|
||||||
|
if allowIndeces {
|
||||||
|
tk = NewToken(listTerm.tk.row, listTerm.tk.col, SymIndex, listTerm.source())
|
||||||
|
root = newTerm(tk)
|
||||||
|
if err = tree.addTerm(root); err == nil {
|
||||||
|
err = tree.addTerm(listTerm)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
root = listTerm
|
||||||
|
err = tree.addTerm(listTerm)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func changePrefix(tk *Token) {
|
||||||
|
switch tk.Sym {
|
||||||
|
case SymMinus:
|
||||||
|
tk.SetSymbol(SymChangeSign)
|
||||||
|
case SymPlus:
|
||||||
|
tk.SetSymbol(SymUnchangeSign)
|
||||||
|
case SymStar:
|
||||||
|
tk.SetSymbol(SymDereference)
|
||||||
|
case SymExclamation:
|
||||||
|
tk.SetSymbol(SymNot)
|
||||||
|
case SymDoublePlus:
|
||||||
|
tk.SetSymbol(SymPreInc)
|
||||||
|
case SymDoubleMinus:
|
||||||
|
tk.SetSymbol(SymPreDec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (parser *parser) parseGeneral(scanner *scanner, ctx parserContext, 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 = parser.Next(scanner); err == nil && tk != nil && !tk.IsTerm(termSymbols); /*&& !areSymbolsOutOfCtx(tk, selectorTerm, SymColon, SymDoubleColon)*/ tk = parser.Next(scanner) {
|
||||||
if tk.Sym == SymComment {
|
// if tk.Sym == SymComment {
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
|
|
||||||
if tk.Sym == SymSemiColon {
|
if tk.Sym == SymSemiColon {
|
||||||
if allowForest {
|
if hasFlag(ctx, allowMultiExpr) {
|
||||||
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)
|
||||||
@ -376,96 +454,118 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
|||||||
|
|
||||||
//fmt.Println("Token:", tk)
|
//fmt.Println("Token:", tk)
|
||||||
if firstToken {
|
if firstToken {
|
||||||
if tk.Sym == SymMinus {
|
changePrefix(tk)
|
||||||
tk.Sym = SymChangeSign
|
// if tk.Sym == SymMinus {
|
||||||
} else if tk.Sym == SymPlus {
|
// tk.Sym = SymChangeSign
|
||||||
tk.Sym = SymUnchangeSign
|
// } else if tk.Sym == SymPlus {
|
||||||
}
|
// tk.Sym = SymUnchangeSign
|
||||||
|
// } else if tk.IsSymbol(SymStar) {
|
||||||
|
// tk.SetSymbol(SymDereference)
|
||||||
|
// } else if tk.IsSymbol(SymExclamation) {
|
||||||
|
// tk.SetSymbol(SymNot)
|
||||||
|
// }
|
||||||
firstToken = false
|
firstToken = false
|
||||||
}
|
}
|
||||||
|
|
||||||
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, ctx, SymClosedRound); err == nil {
|
||||||
subTree.root.priority = priValue
|
exprTerm := newExprTerm(subTree.root)
|
||||||
err = tree.addTerm(subTree.root)
|
err = tree.addTerm(exprTerm)
|
||||||
currentTerm = subTree.root
|
currentTerm = exprTerm
|
||||||
|
// subTree.root.priority = priValue
|
||||||
|
// err = tree.addTerm(newExprTerm(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, ctx, 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 {
|
newCtx := addFlagsCond(addFlags(ctx, squareContext), allowIndex, couldBeACollection(currentTerm))
|
||||||
err = tree.addTerm(listTerm)
|
if listTerm, err = parser.parseList(scanner, newCtx); err == nil {
|
||||||
currentTerm = listTerm
|
currentTerm, err = listSubTree(tree, listTerm, hasFlag(newCtx, allowIndex))
|
||||||
}
|
}
|
||||||
case SymOpenBrace:
|
case SymOpenBrace:
|
||||||
var mapTerm *term
|
if currentTerm != nil && currentTerm.symbol() == SymColon {
|
||||||
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
|
err = currentTerm.Errorf(`selector-case outside of a selector context`)
|
||||||
err = tree.addTerm(mapTerm)
|
} else {
|
||||||
currentTerm = mapTerm
|
var mapTerm *term
|
||||||
}
|
if mapTerm, err = parser.parseDictionary(scanner, ctx); err == nil {
|
||||||
case SymEqual:
|
err = tree.addTerm(mapTerm)
|
||||||
if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
|
currentTerm = mapTerm
|
||||||
currentTerm, err = tree.addToken2(tk)
|
}
|
||||||
}
|
}
|
||||||
|
case SymEqual, SymPlusEqual, SymMinusEqual, SymStarEqual, SymSlashEqual, SymPercEqual, SymAmpersandEqual, SymVertBarEqual, SymDoubleLessEqual, SymDoubleGreaterEqual, SymCaretEqual:
|
||||||
|
currentTerm, err = tree.addToken(tk)
|
||||||
|
firstToken = true
|
||||||
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, ctx); err == nil {
|
||||||
err = tree.addTerm(iterDefTerm)
|
err = tree.addTerm(iterDefTerm)
|
||||||
currentTerm = iterDefTerm
|
currentTerm = iterDefTerm
|
||||||
}
|
}
|
||||||
case SymIdentifier:
|
case SymIdentifier:
|
||||||
if tk.source[0] == '@' && !allowVarRef {
|
if tk.source[0] == '@' && !hasFlag(ctx, allowVarRef) {
|
||||||
err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source)
|
err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source)
|
||||||
} else {
|
} else {
|
||||||
currentTerm, err = tree.addToken2(tk)
|
currentTerm, err = tree.addToken(tk)
|
||||||
}
|
}
|
||||||
case SymQuestion:
|
case SymQuestion:
|
||||||
if selectorTerm, err = self.parseSelector(scanner, tree, allowVarRef); err == nil {
|
if selectorTerm, err = parser.parseSelector(scanner, tree, ctx); err == nil {
|
||||||
currentTerm = selectorTerm
|
currentTerm = selectorTerm
|
||||||
|
addFlags(ctx, selectorContext)
|
||||||
}
|
}
|
||||||
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, ctx, 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.addToken(tk)
|
||||||
|
if tk.IsOneOfA(SymColon, SymRange) {
|
||||||
|
// Colon outside a selector term acts like a separator
|
||||||
|
firstToken = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
currentTerm, err = tree.addToken2(tk)
|
currentTerm, err = tree.addToken(tk)
|
||||||
}
|
}
|
||||||
|
|
||||||
if currentTerm != nil && currentTerm.tk.Sym != SymSelector && currentTerm.parent != nil && currentTerm.parent.tk.Sym != SymSelector {
|
if currentTerm != nil && currentTerm.tk.Sym != SymSelector && currentTerm.parent != nil && currentTerm.parent.tk.Sym != SymSelector {
|
||||||
selectorTerm = nil
|
selectorTerm = nil
|
||||||
|
remFlags(ctx, selectorContext)
|
||||||
}
|
}
|
||||||
lastSym = tk.Sym
|
// lastSym = tk.Sym
|
||||||
}
|
}
|
||||||
if err == nil {
|
|
||||||
err = tk.Error()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkPrevSymbol(lastSym, wantedSym Symbol, tk *Token) (err error) {
|
if err == nil {
|
||||||
if lastSym != wantedSym {
|
if !tk.IsOneOf(termSymbols) {
|
||||||
err = fmt.Errorf(`assign operator (%q) must be preceded by a variable`, tk.source)
|
var symDesc string
|
||||||
|
if tk.IsSymbol(SymError) {
|
||||||
|
symDesc = tk.ErrorText()
|
||||||
|
} else {
|
||||||
|
symDesc = SymToString(tk.Sym)
|
||||||
|
}
|
||||||
|
err = tk.ErrorExpectedGotStringWithPrefix("expected one of", SymListToString(termSymbols, true), symDesc)
|
||||||
|
} else {
|
||||||
|
err = tk.Error()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
221
parser_test.go
221
parser_test.go
@ -1,221 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// parser_test.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type inputType struct {
|
|
||||||
source string
|
|
||||||
wantResult any
|
|
||||||
wantErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
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},
|
|
||||||
/* 138 */ {`v=10; v++; v`, int64(11), nil},
|
|
||||||
/* 139 */ {`1+1|2+0.5`, float64(2), nil},
|
|
||||||
/* 140 */ {`1.2()`, newFraction(6, 5), nil},
|
|
||||||
/* 141 */ {`1|(2-2)`, nil, errors.New(`division by zero`)},
|
|
||||||
}
|
|
||||||
|
|
||||||
// t.Setenv("EXPR_PATH", ".")
|
|
||||||
parserTest(t, "General", inputs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parserTest(t *testing.T, section string, inputs []inputType) {
|
|
||||||
|
|
||||||
succeeded := 0
|
|
||||||
failed := 0
|
|
||||||
|
|
||||||
for i, input := range inputs {
|
|
||||||
var expr Expr
|
|
||||||
var gotResult any
|
|
||||||
var gotErr error
|
|
||||||
|
|
||||||
ctx := NewSimpleFuncStore()
|
|
||||||
parser := NewParser(ctx)
|
|
||||||
|
|
||||||
logTest(t, i+1, section, input.source, input.wantResult, input.wantErr)
|
|
||||||
|
|
||||||
r := strings.NewReader(input.source)
|
|
||||||
scanner := NewScanner(r, DefaultTranslations())
|
|
||||||
|
|
||||||
good := true
|
|
||||||
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
|
|
||||||
gotResult, gotErr = expr.Eval(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
eq := reflect.DeepEqual(gotResult, input.wantResult)
|
|
||||||
|
|
||||||
if !eq /*gotResult != input.wantResult*/ {
|
|
||||||
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
|
||||||
good = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if gotErr != input.wantErr {
|
|
||||||
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
|
|
||||||
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
|
|
||||||
good = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
131
plugins.go
Normal file
131
plugins.go
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// plugin.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"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 importPluginFromSearchPath(name any) (count int, err error) {
|
||||||
|
var moduleSpec any
|
||||||
|
|
||||||
|
dirList := buildSearchDirList("plugin", ENV_EXPR_PLUGIN_PATH)
|
||||||
|
count = 0
|
||||||
|
it := NewAnyIterator(name)
|
||||||
|
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 = fmt.Errorf("expected string as item nr %d, got %s", it.Index()+1, TypeName(moduleSpec))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
450
scanner.go
450
scanner.go
@ -16,6 +16,7 @@ import (
|
|||||||
type scanner struct {
|
type scanner struct {
|
||||||
current *Token
|
current *Token
|
||||||
prev *Token
|
prev *Token
|
||||||
|
stage *Token
|
||||||
stream *bufio.Reader
|
stream *bufio.Reader
|
||||||
row int
|
row int
|
||||||
column int
|
column int
|
||||||
@ -39,9 +40,9 @@ func DefaultTranslations() map[Symbol]Symbol {
|
|||||||
SymKwAnd: SymAnd,
|
SymKwAnd: SymAnd,
|
||||||
SymDoubleVertBar: SymOr,
|
SymDoubleVertBar: SymOr,
|
||||||
SymKwOr: SymOr,
|
SymKwOr: SymOr,
|
||||||
SymTilde: SymNot,
|
// SymTilde: SymNot,
|
||||||
SymKwNot: SymNot,
|
SymKwNot: SymNot,
|
||||||
SymLessGreater: SymNotEqual,
|
SymLessGreater: SymNotEqual,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,242 +50,306 @@ 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) UnreadToken() (err error) {
|
||||||
return self.prev
|
if scanner.stage == nil {
|
||||||
|
scanner.stage = scanner.current
|
||||||
|
scanner.current = scanner.prev
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("staging already present, currently one level only of staging is allowed")
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) Next() (tk *Token) {
|
func (scanner *scanner) lastPos() (r, c int) {
|
||||||
self.prev = self.current
|
if scanner.prev != nil {
|
||||||
tk = self.current
|
r = scanner.prev.row
|
||||||
self.current = self.fetchNextToken()
|
c = scanner.prev.col
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scanner *scanner) Previous() *Token {
|
||||||
|
return scanner.prev
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scanner *scanner) Next() (tk *Token) {
|
||||||
|
scanner.prev = scanner.current
|
||||||
|
tk = scanner.current
|
||||||
|
if scanner.stage != nil {
|
||||||
|
scanner.current = scanner.stage
|
||||||
|
scanner.stage = nil
|
||||||
|
} else {
|
||||||
|
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 if next == '>' {
|
||||||
|
tk = scanner.moveOn(SymPlusGreater, 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 if next, _ = scanner.peek(); next == '=' {
|
||||||
|
tk = scanner.moveOn(SymStarEqual, 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, _ = scanner.peek(); next == '=' {
|
||||||
|
tk = scanner.moveOn(SymSlashEqual, ch, next)
|
||||||
} 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 if next, _ = scanner.peek(); next == '=' {
|
||||||
|
tk = scanner.moveOn(SymVertBarEqual, 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)
|
if next, _ := scanner.peek(); next == '=' {
|
||||||
case ':':
|
tk = scanner.moveOn(SymCaretEqual, ch, next)
|
||||||
if next, _ := self.peek(); next == ':' {
|
|
||||||
tk = self.moveOn(SymDoubleColon, ch, next)
|
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymColon, ch)
|
tk = scanner.makeToken(SymCaret, ch)
|
||||||
|
}
|
||||||
|
case ':':
|
||||||
|
if next, _ := scanner.peek(); next == ':' {
|
||||||
|
tk = scanner.moveOn(SymDoubleColon, ch, next)
|
||||||
|
} else {
|
||||||
|
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 if next, _ = scanner.peek(); next == '=' {
|
||||||
|
tk = scanner.moveOn(SymAmpersandEqual, ch, next)
|
||||||
} else {
|
} else {
|
||||||
tk = self.makeToken(SymAmpersand, ch)
|
tk = scanner.makeToken(SymAmpersand, ch)
|
||||||
}
|
}
|
||||||
case '%':
|
case '%':
|
||||||
tk = self.makeToken(SymPercent, ch)
|
if next, _ := scanner.peek(); next == '=' {
|
||||||
|
tk = scanner.moveOn(SymPercEqual, ch, next)
|
||||||
|
} else {
|
||||||
|
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)
|
scanner.readChar()
|
||||||
|
next2, _ := scanner.readChar()
|
||||||
|
scanner.unreadChar()
|
||||||
|
if next2 == '=' {
|
||||||
|
tk = scanner.moveOn(SymDoubleLessEqual, ch, next, next2)
|
||||||
|
} else {
|
||||||
|
tk = scanner.accept(SymDoubleLess, ch, next)
|
||||||
|
}
|
||||||
} else if next == '>' {
|
} else if next == '>' {
|
||||||
tk = self.moveOn(SymLessGreater, ch, next)
|
tk = scanner.moveOn(SymLessGreater, ch, next)
|
||||||
|
} else if next == '+' {
|
||||||
|
tk = scanner.moveOn(SymLessPlus, 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)
|
scanner.readChar()
|
||||||
|
next2, _ := scanner.readChar()
|
||||||
|
scanner.unreadChar()
|
||||||
|
if next2 == '=' {
|
||||||
|
tk = scanner.moveOn(SymDoubleGreaterEqual, ch, next, next2)
|
||||||
|
} else {
|
||||||
|
tk = scanner.accept(SymDoubleGreater, 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 +357,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 +385,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 +424,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 +433,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,9 +443,9 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -389,32 +454,32 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
|
|||||||
if ch == 'e' || ch == 'E' {
|
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 = fmt.Errorf("[%d:%d] expected integer exponent, got %c", self.row, self.column, ch)
|
err = fmt.Errorf("[%d:%d] expected integer exponent, got %c", scanner.row, scanner.column, ch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if ch == '(' {
|
} else if ch == '(' {
|
||||||
sym = SymFraction
|
sym = SymFraction
|
||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
ch, err = self.readChar()
|
ch, err = scanner.readChar()
|
||||||
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 {
|
if err == nil {
|
||||||
if ch != ')' {
|
if ch != ')' {
|
||||||
err = fmt.Errorf("[%d:%d] expected ')', got '%c'", self.row, self.column, ch)
|
err = fmt.Errorf("[%d:%d] expected ')', got '%c'", scanner.row, scanner.column, ch)
|
||||||
} else {
|
} else {
|
||||||
sb.WriteByte(ch)
|
sb.WriteByte(ch)
|
||||||
_, err = self.readChar()
|
_, err = scanner.readChar()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -422,10 +487,10 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
_ = 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)
|
||||||
@ -435,39 +500,39 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
|
|||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -487,29 +552,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 {
|
||||||
@ -518,18 +583,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 '"':
|
||||||
@ -546,7 +611,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
|
||||||
@ -557,65 +622,72 @@ 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(string(termCh)))
|
||||||
} 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()
|
if len(chars) > 1 {
|
||||||
|
scanner.readChar()
|
||||||
}
|
}
|
||||||
|
// }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) makeToken(sym Symbol, chars ...byte) (tk *Token) {
|
func (scanner *scanner) accept(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) makeToken(sym Symbol, chars ...byte) (tk *Token) {
|
||||||
tk = NewToken(self.row, self.column, self.translate(sym), upperCaseKeyword)
|
tk = NewToken(scanner.row, scanner.column, scanner.translate(sym), string(chars))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) makeValueToken(sym Symbol, source string, value any) (tk *Token) {
|
func (scanner *scanner) makeKeywordToken(sym Symbol, upperCaseKeyword string) (tk *Token) {
|
||||||
tk = NewValueToken(self.row, self.column, self.translate(sym), source, value)
|
tk = NewToken(scanner.row, scanner.column, scanner.translate(sym), upperCaseKeyword)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *scanner) makeErrorToken(err error) *Token {
|
func (scanner *scanner) makeValueToken(sym Symbol, source string, value any) (tk *Token) {
|
||||||
return NewErrorToken(self.row, self.column, err)
|
tk = NewValueToken(scanner.row, scanner.column, scanner.translate(sym), source, value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scanner *scanner) makeErrorToken(err error) *Token {
|
||||||
|
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
|
|
||||||
}
|
|
191
simple-store.go
Normal file
191
simple-store.go
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
// 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) (exprFunc ExprFunc, err error) {
|
||||||
|
var info *funcInfo
|
||||||
|
if info, err = newFuncInfo(name, functor, returnType, params); err == nil {
|
||||||
|
ctx.funcStore[name] = info
|
||||||
|
exprFunc = 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 map[string]any) (result any, err error) {
|
||||||
|
if info, exists := GetLocalFuncInfo(ctx, name); exists {
|
||||||
|
functor := info.Functor()
|
||||||
|
result, err = functor.InvokeNamed(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()
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
||||||
// All rights reserved.
|
|
||||||
|
|
||||||
// strings_test.go
|
|
||||||
package expr
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestStringsParser(t *testing.T) {
|
|
||||||
inputs := []inputType{
|
|
||||||
/* 1 */ {`"uno" + "due"`, `unodue`, nil},
|
|
||||||
/* 2 */ {`"uno" + 2`, `uno2`, nil},
|
|
||||||
/* 3 */ {`"uno" + (2+1)`, `uno3`, nil},
|
|
||||||
/* 4 */ {`"uno" * (2+1)`, `unounouno`, nil},
|
|
||||||
/* 5 */ {`"abc".1`, `b`, nil},
|
|
||||||
/* 5 */ {`#"abc"`, int64(3), nil},
|
|
||||||
}
|
|
||||||
parserTest(t, "String", inputs)
|
|
||||||
}
|
|
198
symbol-map.go
Normal file
198
symbol-map.go
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// Symbol.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var symbolMap map[Symbol]symbolSpec
|
||||||
|
|
||||||
|
type symbolClass int16
|
||||||
|
|
||||||
|
const (
|
||||||
|
symClassOperator symbolClass = iota
|
||||||
|
symClassCommand
|
||||||
|
symClassIdentifier
|
||||||
|
symClassDelimiter
|
||||||
|
symClassParenthesis
|
||||||
|
symClassDeclaration
|
||||||
|
symClassValue
|
||||||
|
symClassOther
|
||||||
|
)
|
||||||
|
|
||||||
|
type symbolSpec struct {
|
||||||
|
repr string
|
||||||
|
kind symbolClass
|
||||||
|
opType termPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
|
||||||
|
symbolMap = map[Symbol]symbolSpec{
|
||||||
|
SymUnknown: {"<unknown>", symClassOther, posLeaf}, // -1: Unknown symbol
|
||||||
|
SymNone: {"<null>", symClassOther, posLeaf}, // 0: Null value for variable of type symbol
|
||||||
|
SymError: {"<error>", symClassOther, posLeaf}, // 1: Error reading from stream
|
||||||
|
SymEos: {"<eos>", symClassOther, posLeaf}, // 2: End of stream
|
||||||
|
SymMinus: {"-", symClassOperator, posInfix}, // 3: '-'
|
||||||
|
SymMinusEqual: {"-=", symClassOperator, posInfix}, // 4: '-='
|
||||||
|
SymDoubleMinus: {"--", symClassOperator, posPostfix}, // 5: '--'
|
||||||
|
SymPlus: {"+", symClassOperator, posInfix}, // 6: '+'
|
||||||
|
SymPlusEqual: {"+=", symClassOperator, posInfix}, // 7: '+='
|
||||||
|
SymDoublePlus: {"++", symClassOperator, posPostfix}, // 8: '++'
|
||||||
|
SymStar: {"*", symClassOperator, posInfix}, // 9: '*'
|
||||||
|
SymDoubleStar: {"**", symClassOperator, posInfix}, // 10: '**'
|
||||||
|
SymSlash: {"/", symClassOperator, posInfix}, // 11: '/'
|
||||||
|
SymBackSlash: {"\\", symClassOperator, posLeaf}, // 12: '\'
|
||||||
|
SymVertBar: {"|", symClassOperator, posInfix}, // 13: '|'
|
||||||
|
SymDoubleVertBar: {"||", symClassOperator, posInfix}, // 14: '||'
|
||||||
|
SymComma: {",", symClassOperator, posInfix}, // 15: ','
|
||||||
|
SymColon: {":", symClassOperator, posInfix}, // 16: ':'
|
||||||
|
SymSemiColon: {";", symClassOperator, posInfix}, // 17: ';'
|
||||||
|
SymDot: {".", symClassOperator, posInfix}, // 18: '.'
|
||||||
|
SymDotSlash: {"./", symClassOperator, posInfix}, // 19: './'
|
||||||
|
SymQuote: {"'", symClassDelimiter, posLeaf}, // 20: '\''
|
||||||
|
SymDoubleQuote: {"\"", symClassDelimiter, posLeaf}, // 21: '"'
|
||||||
|
SymBackTick: {"`", symClassDelimiter, posLeaf}, // 22: '`'
|
||||||
|
SymExclamation: {"!", symClassOperator, posPostfix}, // 23: '!'
|
||||||
|
SymQuestion: {"?", symClassOperator, posInfix}, // 24: '?'
|
||||||
|
SymAmpersand: {"&", symClassOperator, posInfix}, // 25: '&'
|
||||||
|
SymDoubleAmpersand: {"&&", symClassOperator, posInfix}, // 26: '&&'
|
||||||
|
SymPercent: {"%", symClassOperator, posInfix}, // 27: '%'
|
||||||
|
SymAt: {"@", symClassOperator, posPrefix}, // 28: '@'
|
||||||
|
SymUndescore: {"_", symClassIdentifier, posLeaf}, // 29: '_'
|
||||||
|
SymEqual: {"=", symClassOperator, posInfix}, // 30: '='
|
||||||
|
SymDoubleEqual: {"==", symClassOperator, posInfix}, // 31: '=='
|
||||||
|
SymLess: {"<", symClassOperator, posInfix}, // 32: '<'
|
||||||
|
SymLessOrEqual: {"<=", symClassOperator, posInfix}, // 33: '<='
|
||||||
|
SymGreater: {">", symClassOperator, posInfix}, // 34: '>'
|
||||||
|
SymGreaterOrEqual: {">=", symClassOperator, posInfix}, // 35: '>='
|
||||||
|
SymLessGreater: {"<>", symClassOperator, posInfix}, // 36: '<>'
|
||||||
|
SymNotEqual: {"!=", symClassOperator, posInfix}, // 37: '!='
|
||||||
|
SymDollar: {"$", symClassOperator, posPrefix}, // 38: '$'
|
||||||
|
SymHash: {"#", symClassOperator, posPrefix}, // 39: '#'
|
||||||
|
SymOpenRound: {"(", symClassParenthesis, posPrefix}, // 40: '('
|
||||||
|
SymClosedRound: {")", symClassParenthesis, posPostfix}, // 41: ')'
|
||||||
|
SymOpenSquare: {"[", symClassParenthesis, posPrefix}, // 42: '['
|
||||||
|
SymClosedSquare: {"]", symClassParenthesis, posPostfix}, // 43: ']'
|
||||||
|
SymOpenBrace: {"{", symClassParenthesis, posPrefix}, // 44: '{'
|
||||||
|
SymClosedBrace: {"}", symClassParenthesis, posPostfix}, // 45: '}'
|
||||||
|
SymTilde: {"~", symClassOperator, posPrefix}, // 46: '~'
|
||||||
|
SymDoubleQuestion: {"??", symClassOperator, posInfix}, // 47: '??'
|
||||||
|
SymQuestionEqual: {"?=", symClassOperator, posInfix}, // 48: '?='
|
||||||
|
SymQuestionExclam: {"?!", symClassOperator, posInfix}, // 49: '?!'
|
||||||
|
SymDoubleAt: {"@@", symClassCommand, posLeaf}, // 50: '@@'
|
||||||
|
SymDoubleColon: {"::", symClassOperator, posInfix}, // 51: '::'
|
||||||
|
SymDoubleGreater: {">>", symClassOperator, posInfix}, // 52: '>>'
|
||||||
|
SymDoubleLess: {"<<", symClassOperator, posInfix}, // 53: '<<'
|
||||||
|
SymCaret: {"^", symClassOperator, posInfix}, // 54: '^'
|
||||||
|
SymDollarRound: {"$(", symClassOperator, posPrefix}, // 55: '$('
|
||||||
|
SymOpenClosedRound: {"()", symClassOperator, posPostfix}, // 56: '()'
|
||||||
|
SymDoubleDollar: {"$$", symClassCommand, posLeaf}, // 57: '$$'
|
||||||
|
SymDoubleDot: {"..", symClassOperator, posInfix}, // 58: '..'
|
||||||
|
SymTripleDot: {"...", symClassOperator, posPostfix}, // 59: '...'
|
||||||
|
SymStarEqual: {"*=", symClassOperator, posInfix}, // 60: '*='
|
||||||
|
SymSlashEqual: {"/=", symClassOperator, posInfix}, // 61: '/='
|
||||||
|
SymPercEqual: {"%=", symClassOperator, posInfix}, // 62: '%='
|
||||||
|
SymDoubleLessEqual: {"<<=", symClassOperator, posInfix}, // 63: '<<='
|
||||||
|
SymDoubleGreaterEqual: {">>=", symClassOperator, posInfix}, // 64: '>>='
|
||||||
|
SymAmpersandEqual: {"&=", symClassOperator, posInfix}, // 65: '&='
|
||||||
|
SymVertBarEqual: {"|=", symClassOperator, posInfix}, // 65: '|='
|
||||||
|
SymCaretEqual: {"^=", symClassOperator, posInfix}, // 66: '^='
|
||||||
|
SymPlusGreater: {"+>", symClassOperator, posInfix}, // 67: '+>'
|
||||||
|
SymLessPlus: {"<+", symClassOperator, posInfix}, // 68: '<+'
|
||||||
|
SymPreInc: {"++", symClassOperator, posPrefix}, // : '++'
|
||||||
|
SymPreDec: {"--", symClassOperator, posPrefix}, // : '--'
|
||||||
|
// SymChangeSign
|
||||||
|
// SymUnchangeSign
|
||||||
|
// SymIdentifier
|
||||||
|
// SymBool
|
||||||
|
// SymInteger
|
||||||
|
// SymVariable
|
||||||
|
// SymFloat
|
||||||
|
// SymFraction
|
||||||
|
// SymString
|
||||||
|
// SymIterator
|
||||||
|
// SymOr: "or",
|
||||||
|
// SymAnd: "and",
|
||||||
|
// SymNot: "not",
|
||||||
|
// SymComment
|
||||||
|
// SymFuncCall
|
||||||
|
// SymFuncDef
|
||||||
|
// SymList
|
||||||
|
// SymDict
|
||||||
|
// SymIndex
|
||||||
|
// SymExpression
|
||||||
|
// SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
|
||||||
|
// SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
|
||||||
|
// // SymOpenComment // 0: '/*'
|
||||||
|
// // SymClosedComment // 0: '*/'
|
||||||
|
// // SymOneLineComment // 0: '//'
|
||||||
|
// keywordBase
|
||||||
|
SymKwAnd: {"and", symClassOperator, posInfix},
|
||||||
|
SymKwNot: {"not", symClassOperator, posInfix},
|
||||||
|
SymKwOr: {"or", symClassOperator, posInfix},
|
||||||
|
SymKwBut: {"but", symClassOperator, posInfix},
|
||||||
|
SymKwFunc: {"func(", symClassDeclaration, posPrefix},
|
||||||
|
SymKwBuiltin: {"builtin", symClassOperator, posPrefix},
|
||||||
|
SymKwPlugin: {"plugin", symClassOperator, posPrefix},
|
||||||
|
SymKwIn: {"in", symClassOperator, posInfix},
|
||||||
|
SymKwInclude: {"include", symClassOperator, posPrefix},
|
||||||
|
SymKwNil: {"nil", symClassValue, posLeaf},
|
||||||
|
SymKwUnset: {"unset", symClassOperator, posPrefix},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SymToString(sym Symbol) string {
|
||||||
|
if s, ok := symbolMap[sym]; ok {
|
||||||
|
return s.repr
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func SymListToString(symList []Symbol, quote bool) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
if len(symList) == 0 {
|
||||||
|
sb.WriteString("<nothing>")
|
||||||
|
} else {
|
||||||
|
for _, sym := range symList {
|
||||||
|
if sb.Len() > 0 {
|
||||||
|
sb.WriteByte(',')
|
||||||
|
sb.WriteByte(' ')
|
||||||
|
}
|
||||||
|
if quote {
|
||||||
|
sb.WriteByte('`')
|
||||||
|
}
|
||||||
|
sb.WriteString(SymToString(sym))
|
||||||
|
if quote {
|
||||||
|
sb.WriteByte('`')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringEndsWithOperator(s string) bool {
|
||||||
|
return endingOperator(s) != SymNone
|
||||||
|
}
|
||||||
|
|
||||||
|
func endingOperator(s string) (sym Symbol) {
|
||||||
|
var matchLength = 0
|
||||||
|
sym = SymNone
|
||||||
|
lower := strings.TrimRight(strings.ToLower(s), " \t")
|
||||||
|
for symbol, spec := range symbolMap {
|
||||||
|
if strings.HasSuffix(lower, spec.repr) {
|
||||||
|
if len(spec.repr) > matchLength {
|
||||||
|
matchLength = len(spec.repr)
|
||||||
|
if spec.kind == symClassOperator && (spec.opType == posInfix || spec.opType == posPrefix) {
|
||||||
|
sym = symbol
|
||||||
|
} else {
|
||||||
|
sym = SymNone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
141
symbol.go
141
symbol.go
@ -7,71 +7,86 @@ package expr
|
|||||||
type Symbol int16
|
type Symbol int16
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SymUnknown Symbol = iota - 1 // -1: Unknown symbol
|
SymUnknown Symbol = iota - 1 // -1: Unknown symbol
|
||||||
SymNone // 0: Null value for variable of type symbol
|
SymNone // 0: Null value for variable of type symbol
|
||||||
SymError // 1: Error reading from stream
|
SymError // 1: Error reading from stream
|
||||||
SymEos // 2: End of stream
|
SymEos // 2: End of stream
|
||||||
SymMinus // 3: '-'
|
SymMinus // 3: '-'
|
||||||
SymMinusEqual // 4: '-='
|
SymMinusEqual // 4: '-='
|
||||||
SymDoubleMinus // 5: '--'
|
SymDoubleMinus // 5: '--'
|
||||||
SymPlus // 6: '+'
|
SymPlus // 6: '+'
|
||||||
SymPlusEqual // 7: '+='
|
SymPlusEqual // 7: '+='
|
||||||
SymDoublePlus // 8: '++'
|
SymDoublePlus // 8: '++'
|
||||||
SymStar // 9: '*'
|
SymStar // 9: '*'
|
||||||
SymDoubleStar // 10: '**'
|
SymDoubleStar // 10: '**'
|
||||||
SymSlash // 11: '/'
|
SymSlash // 11: '/'
|
||||||
SymBackSlash // 12: '\'
|
SymBackSlash // 12: '\'
|
||||||
SymVertBar // 13: '|'
|
SymVertBar // 13: '|'
|
||||||
SymDoubleVertBar // 14: '||'
|
SymDoubleVertBar // 14: '||'
|
||||||
SymComma // 15: ','
|
SymComma // 15: ','
|
||||||
SymColon // 16: ':'
|
SymColon // 16: ':'
|
||||||
SymSemiColon // 17: ';'
|
SymSemiColon // 17: ';'
|
||||||
SymDot // 18: '.'
|
SymDot // 18: '.'
|
||||||
SymDotSlash // 19: './'
|
SymDotSlash // 19: './'
|
||||||
SymQuote // 20: '\''
|
SymQuote // 20: '\''
|
||||||
SymDoubleQuote // 21: '"'
|
SymDoubleQuote // 21: '"'
|
||||||
SymBackTick // 22: '`'
|
SymBackTick // 22: '`'
|
||||||
SymExclamation // 23: '!'
|
SymExclamation // 23: '!'
|
||||||
SymQuestion // 24: '?'
|
SymQuestion // 24: '?'
|
||||||
SymAmpersand // 25: '&'
|
SymAmpersand // 25: '&'
|
||||||
SymDoubleAmpersand // 26: '&&'
|
SymDoubleAmpersand // 26: '&&'
|
||||||
SymPercent // 27: '%'
|
SymPercent // 27: '%'
|
||||||
SymAt // 28: '@'
|
SymAt // 28: '@'
|
||||||
SymUndescore // 29: '_'
|
SymUndescore // 29: '_'
|
||||||
SymEqual // 30: '='
|
SymEqual // 30: '='
|
||||||
SymDoubleEqual // 31: '=='
|
SymDoubleEqual // 31: '=='
|
||||||
SymLess // 32: '<'
|
SymLess // 32: '<'
|
||||||
SymLessOrEqual // 33: '<='
|
SymLessOrEqual // 33: '<='
|
||||||
SymGreater // 34: '>'
|
SymGreater // 34: '>'
|
||||||
SymGreaterOrEqual // 35: '>='
|
SymGreaterOrEqual // 35: '>='
|
||||||
SymLessGreater // 36: '<>'
|
SymLessGreater // 36: '<>'
|
||||||
SymNotEqual // 37: '!='
|
SymNotEqual // 37: '!='
|
||||||
SymDollar // 38: '$'
|
SymDollar // 38: '$'
|
||||||
SymHash // 39: '#'
|
SymHash // 39: '#'
|
||||||
SymOpenRound // 40: '('
|
SymOpenRound // 40: '('
|
||||||
SymClosedRound // 41: ')'
|
SymClosedRound // 41: ')'
|
||||||
SymOpenSquare // 42: '['
|
SymOpenSquare // 42: '['
|
||||||
SymClosedSquare // 43: ']'
|
SymClosedSquare // 43: ']'
|
||||||
SymOpenBrace // 44: '{'
|
SymOpenBrace // 44: '{'
|
||||||
SymClosedBrace // 45: '}'
|
SymClosedBrace // 45: '}'
|
||||||
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: '<<'
|
SymDoubleGreater // 52: '>>'
|
||||||
SymCaret // 53: '^'
|
SymDoubleLess // 53: '<<'
|
||||||
SymDollarRound // 54: '$('
|
SymCaret // 54: '^'
|
||||||
SymOpenClosedRound // 55: '()'
|
SymDollarRound // 55: '$('
|
||||||
SymDoubleDollar // 56: '$$'
|
SymOpenClosedRound // 56: '()'
|
||||||
SymDoubleDot // 57: '..'
|
SymDoubleDollar // 57: '$$'
|
||||||
SymTripleDot // 58: '...'
|
SymDoubleDot // 58: '..'
|
||||||
|
SymTripleDot // 59: '...'
|
||||||
|
SymStarEqual // 60: '*='
|
||||||
|
SymSlashEqual // 61: '/='
|
||||||
|
SymPercEqual // 62: '%='
|
||||||
|
SymDoubleLessEqual // 63: '<<='
|
||||||
|
SymDoubleGreaterEqual // 64: '>>='
|
||||||
|
SymAmpersandEqual // 65: '&='
|
||||||
|
SymVertBarEqual // 65: '|='
|
||||||
|
SymCaretEqual // 66: '^='
|
||||||
|
SymPlusGreater // 67: '+>'
|
||||||
|
SymLessPlus // 68: '<+'
|
||||||
SymChangeSign
|
SymChangeSign
|
||||||
SymUnchangeSign
|
SymUnchangeSign
|
||||||
|
SymDereference
|
||||||
|
SymPreInc
|
||||||
|
SymPreDec
|
||||||
SymIdentifier
|
SymIdentifier
|
||||||
SymBool
|
SymBool
|
||||||
SymInteger
|
SymInteger
|
||||||
|
SymVariable
|
||||||
SymFloat
|
SymFloat
|
||||||
SymFraction
|
SymFraction
|
||||||
SymString
|
SymString
|
||||||
@ -84,6 +99,8 @@ const (
|
|||||||
SymFuncDef
|
SymFuncDef
|
||||||
SymList
|
SymList
|
||||||
SymDict
|
SymDict
|
||||||
|
SymIndex
|
||||||
|
SymRange // [index : index]
|
||||||
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> "}"
|
||||||
@ -99,9 +116,11 @@ const (
|
|||||||
SymKwBut
|
SymKwBut
|
||||||
SymKwFunc
|
SymKwFunc
|
||||||
SymKwBuiltin
|
SymKwBuiltin
|
||||||
|
SymKwPlugin
|
||||||
SymKwIn
|
SymKwIn
|
||||||
SymKwInclude
|
SymKwInclude
|
||||||
SymKwNil
|
SymKwNil
|
||||||
|
SymKwUnset
|
||||||
)
|
)
|
||||||
|
|
||||||
var keywords map[string]Symbol
|
var keywords map[string]Symbol
|
||||||
@ -111,6 +130,7 @@ func init() {
|
|||||||
keywords = map[string]Symbol{
|
keywords = map[string]Symbol{
|
||||||
"AND": SymKwAnd,
|
"AND": SymKwAnd,
|
||||||
"BUILTIN": SymKwBuiltin,
|
"BUILTIN": SymKwBuiltin,
|
||||||
|
"PLUGIN": SymKwPlugin,
|
||||||
"BUT": SymKwBut,
|
"BUT": SymKwBut,
|
||||||
"FUNC": SymKwFunc,
|
"FUNC": SymKwFunc,
|
||||||
"IN": SymKwIn,
|
"IN": SymKwIn,
|
||||||
@ -118,5 +138,6 @@ func init() {
|
|||||||
"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 (
|
||||||
@ -47,7 +47,7 @@ func TestAddUnknownTokens(t *testing.T) {
|
|||||||
wantErr := errors.New(`unexpected token "%"`)
|
wantErr := errors.New(`unexpected token "%"`)
|
||||||
|
|
||||||
tree := NewAst()
|
tree := NewAst()
|
||||||
if gotErr := tree.addToken(tk0); gotErr != nil && gotErr.Error() != wantErr.Error() {
|
if _, gotErr := tree.addToken(tk0); gotErr != nil && gotErr.Error() != wantErr.Error() {
|
||||||
t.Errorf("err: got <%v>, want <%v>", gotErr, wantErr)
|
t.Errorf("err: got <%v>, want <%v>", gotErr, wantErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
67
t_bool_test.go
Normal file
67
t_bool_test.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// 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`)},
|
||||||
|
/* 15 */ {`!true`, false, nil},
|
||||||
|
/* 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, 15)
|
||||||
|
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)
|
||||||
|
}
|
67
t_builtin-base_test.go
Normal file
67
t_builtin-base_test.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// 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 many 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] expected one of `,`, `)`, got `\"`"},
|
||||||
|
/* 31 */ {`dec()`, nil, `dec(): too few params -- expected 1, got 0`},
|
||||||
|
/* 32 */ {`dec(1,2,3)`, nil, `dec(): too many 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 */ {`eval("a=3"); a`, int64(3), nil},
|
||||||
|
// /* 45 */ {`string([1])`, nil, `string(): can't convert list to string`},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Setenv("EXPR_PATH", ".")
|
||||||
|
|
||||||
|
// runTestSuiteSpec(t, section, inputs, 45)
|
||||||
|
runTestSuite(t, section, inputs)
|
||||||
|
}
|
48
t_builtin-fmt_test.go
Normal file
48
t_builtin-fmt_test.go
Normal file
@ -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(`builtin "fmt"; 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")
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user