Compare commits

..

137 Commits

Author SHA1 Message Date
camoroso cca3b76baa solved a number of problems highlighted by the syntax analyzer 2024-12-27 07:46:11 +01:00
camoroso 24e31997fc context-helpers.go: export functions no longer export control flags 2024-12-27 07:22:28 +01:00
camoroso 646710e180 builtin-base.go: new eval() function 2024-12-27 07:14:26 +01:00
camoroso b38327b841 t_builtin-string_test.go: corrected an undefinite article 2024-12-27 07:14:01 +01:00
camoroso fd912b2eb1 common-errors.go: undefinte article selection in error messages 2024-12-27 07:13:03 +01:00
camoroso 0e55f83d56 Forced the exlamation mark as a postfix operator 2024-12-26 08:57:14 +01:00
camoroso 4725145d1c Doc: changed fraction symbol and introduced binary operators 2024-12-25 07:43:06 +01:00
camoroso edf8818f51 New dedicated priority for binary operators between relational and sum ones 2024-12-25 07:41:08 +01:00
camoroso 6211be8a8f Completed transition of the symbol '|' from fraction to operator binary or. New fraction symbol is ':'.
Also, fixed and improved some parsing sections concerning collection indeces and ranges
2024-12-23 06:59:39 +01:00
camoroso f50ddf48db operator-range.go: range-term registered with symbol SymRange 2024-12-23 06:55:57 +01:00
camoroso 76e01f12d2 term.go: two error messages corrected 2024-12-23 06:53:37 +01:00
camoroso 406bced450 operator-sum.go: sum of two fraction fixed 2024-12-23 06:52:10 +01:00
camoroso 409dc86a92 symbol.go: SymRange added 2024-12-23 06:50:02 +01:00
camoroso 4184221428 symbol-map.go: changed symbol classification of some symbols like quotes and post-op 2024-12-23 06:49:17 +01:00
camoroso 8cf8b36a26 t_parser_test.go: replaced ~ with NOT 2024-12-19 15:36:16 +01:00
camoroso de87050188 scanner.go: removed SymTilde from DefaultTranslatios() -> It is not an alias for the SymNot symbol any more 2024-12-19 15:30:29 +01:00
camoroso a1ec0cc611 All assignment operators set the firstToken flag 2024-12-19 15:27:38 +01:00
camoroso 8e5550bfa7 New operator %= 2024-12-19 15:14:30 +01:00
camoroso 6ee21e10af New, more flexible, parser context datatype that includes and extends
the previous flags allowForest and allowVarRef.
Added binary operator (work in progress).
Better implementation of +=,-=,*=, and /= (new) operators.
2024-12-19 14:48:27 +01:00
camoroso 5c44532790 << && >>: left and right shift with integer operands 2024-12-07 07:06:08 +01:00
camoroso cb66c1ab19 symbol-map.go: removed unsed definitons 2024-10-13 08:44:21 +02:00
camoroso a28d24ba68 parser.go: improved terminal symbols thanks to new symbol-map.go functions 2024-10-13 08:42:55 +02:00
camoroso 523349a204 symbol-map.go: new file that helps to identify symbols by source and class 2024-10-13 08:41:30 +02:00
camoroso b185f1df3a token.go: added a few error functions 2024-10-13 08:39:56 +02:00
camoroso 5da5a61a42 Expr.doc: notes about function context 2024-10-05 05:30:22 +02:00
camoroso 6e9205abc4 t_funcs_test.go: A test add on parameters check about two params with the same name 2024-10-05 05:25:29 +02:00
camoroso f61004fb5d A test added on new implicit boolean cases in selector operator 2024-10-05 05:23:55 +02:00
camoroso 321030c8d3 parser.go: function parameter list can't specify same parameter more than once 2024-10-01 06:39:51 +02:00
camoroso 98fc89e84f operator-selector.go: Simplified selector for Boolean expressions 2024-10-01 06:37:35 +02:00
camoroso 778d00677d Doc: closure example 2024-09-18 20:48:12 +02:00
camoroso ba3dbb7f02 Doc: continuation 2024-09-16 06:52:29 +02:00
camoroso 7285109115 parser.go: number sign is now allowed after the assign operator 2024-09-16 06:49:26 +02:00
camoroso 4755774edd Doc: Fixed a lot of typos 2024-09-12 06:57:43 +02:00
camoroso d215d837f6 Reset() and Clean() have new, simpler signature 2024-09-12 05:44:29 +02:00
camoroso ad3c1e5a60 enhanced and simplified Reset(), Clean() and Next() methods 2024-09-09 15:23:07 +02:00
camoroso d6bf5ee500 common-type-names.go: TypeNil and TyperDict added 2024-09-09 15:16:42 +02:00
camoroso 4b176eb868 Fix function.go: CallFunctionByParams() dit not pass correctly received actual params 2024-08-23 10:29:57 +02:00
camoroso dceb31f542 CallFunction() has been replaced by three new functions:
CallFunctionByTerm(), CallFunctionByArgs() and CallFunctionByParams()
2024-08-02 06:39:33 +02:00
camoroso 075b0b5691 RegisterFunc() also returns the funcInfo object 2024-08-01 00:09:49 +02:00
camoroso 3c51b8d2ee Changed some function names and param types 2024-07-31 09:11:57 +02:00
camoroso a46753f453 Function buildActualParams moved from data-cursor.go ro function.go 2024-07-31 09:08:58 +02:00
camoroso 9070b5c9cc The function parameter model has been modified to support the passing of named parameters 2024-07-28 18:49:08 +02:00
camoroso ab06702e5e operator-post-inc.go: new post int decrement operator '--' 2024-07-24 06:39:35 +02:00
camoroso ffe1fa3aac op-assign expansion now end at ']' and '}' too 2024-07-24 06:37:57 +02:00
camoroso 1a1a475dd8 Added support to op-assign operators like '+='.
This feature is implemented by expansion, not by dedicated operators, e.g. a*=2+x is exapanded as a=a*(2+x)
2024-07-23 15:35:57 +02:00
camoroso 463e3634ba scanner.go: New function UnreadToken().
Currently it only supports one staging level.
2024-07-23 15:32:25 +02:00
camoroso 5f8ca47ef0 term.go: New function Clone() 2024-07-23 15:27:50 +02:00
camoroso 837b887490 token.go: New functions Clone() and IsOneOfA() 2024-07-23 15:27:36 +02:00
camoroso c76e1d3c8e symbol.go: New symbol '*=' 2024-07-23 15:24:54 +02:00
camoroso 315f5b22d3 data-cursor.go: the inizialization of the current item is done in the Next() method.
This allows the application of the filter and map operator to the first item too.
2024-07-23 05:46:37 +02:00
camoroso dfae593e86 operand-iterator.go: removed commented code 2024-07-21 16:35:13 +02:00
camoroso d572f3a129 Iterator: new function Count() 2024-07-21 16:34:52 +02:00
camoroso c461fd138e Iterator defined by data-source now only requires one method: next() 2024-07-21 05:45:22 +02:00
camoroso 6ecbe2feb1 Fixed type (expcted -> expected) 2024-07-21 05:43:55 +02:00
camoroso 80d3c6ec7d parser.go: Fixed an old bug that did not allow the parser to skip comment tokens 2024-07-21 05:37:34 +02:00
camoroso e09806c716 %q replaced by %s in some error messages 2024-07-21 05:33:06 +02:00
camoroso 1a772597cb removed/commented unused code 2024-07-19 17:03:03 +02:00
camoroso 33b3e1fc29 New function for searching and importing plugin 2024-07-19 15:37:00 +02:00
camoroso 4e3f5cfbc6 import-utils.go: Paths are now expanded with respect to env-vars and shell ~ 2024-07-19 15:33:15 +02:00
camoroso e35d4e3f70 utils.go: added function ExpandPath 2024-07-19 15:30:26 +02:00
camoroso b4529499d6 iterator.go: exported some const identifier 2024-07-18 07:27:02 +02:00
camoroso 7745dc24e2 dict-type.go: exported NewDataCursor 2024-07-18 07:25:51 +02:00
camoroso be25385d02 builtin-iterator: changed status variable from 'it_status' to 'status' 2024-07-15 06:59:13 +02:00
camoroso 79889cd8e1 New builtin module 'iterator' 2024-07-14 16:53:32 +02:00
camoroso 234759158c scanner: disabled prefix operator '()' 2024-07-13 18:10:04 +02:00
camoroso 00fda29606 operator-iter-value.go: prefix operator () used to get the current value of an iterator replaced by ^ 2024-07-13 17:17:16 +02:00
camoroso 2a2840bdf2 ListIterator now implements next and current command (e.g it.next) 2024-07-13 17:15:53 +02:00
camoroso 06373f5126 Impemented Typer interface 2024-07-13 17:14:25 +02:00
camoroso e69dad5fb5 Rename priority label priCoalesce as priDefault 2024-07-13 17:13:16 +02:00
camoroso 7459057df9 Expr embeds Typer and ast implements it 2024-07-13 17:11:39 +02:00
camoroso 176969c956 New iterator tests 2024-07-13 16:19:04 +02:00
camoroso cde2efacf1 Test on iterator filter and map 2024-07-13 15:30:04 +02:00
camoroso d7a7b3218c Merge branch 'main' into enhance_iterators 2024-07-13 09:08:16 +02:00
camoroso be3bb12f28 operator-unset.go: fixed function removal 2024-07-13 09:07:33 +02:00
camoroso 032916d4fa New operator unset to delete variables and functions from current context 2024-07-13 09:01:59 +02:00
camoroso f3cc0cc7ad operator-include.go: Fixed inclusion of a list of files.
Now returns the value of the last evaluated expression.
2024-07-13 09:00:53 +02:00
camoroso 905337f963 ExprContext: new functions VarCount(), DeleteVar(), FuncCount(), DeleteFunc() 2024-07-13 08:59:15 +02:00
camoroso 9a95a837f6 data-cursor.go: add item mapping support 2024-07-13 06:44:00 +02:00
camoroso 8547248ea2 data-cursor.go: fixed filter 2024-07-13 00:12:08 +02:00
camoroso a02f998fc6 t_iterator_test.go: Fixed test numbering and add a commentend test that is not yet fulfilled 2024-07-11 07:23:35 +02:00
camoroso e904003e6e minor changes 2024-07-11 06:53:14 +02:00
camoroso 990e04f957 operator-assign.go -- Fix: Assigning a functor to a collection's item didn't work 2024-07-11 06:49:02 +02:00
camoroso d8aed9dd7a t_iterator_test.go: added a test to verify the reset command of the list iterator 2024-07-11 05:54:22 +02:00
camoroso a73d24b171 iter-list.go -> list-iterator.go
Fix: the reset command set the initial item to the second one in the list
2024-07-11 05:52:47 +02:00
camoroso 3ebba83bce term.go: replaced self receiver 2024-07-09 07:50:50 +02:00
camoroso 6b3bfa2a11 self param replaced as opTerm 2024-07-09 07:50:06 +02:00
camoroso 867806155e scanner.go: replaced self receiver 2024-07-08 07:30:58 +02:00
camoroso a711333a2e simple-store.go: commented out an unused function 2024-07-08 07:30:26 +02:00
camoroso af3e946bd4 operator-sum.go: replaced self receiver 2024-07-08 07:29:42 +02:00
camoroso 22a36fa630 context.go renamed as expr-context.go 2024-07-07 16:20:29 +02:00
camoroso 6d9a379c92 token.go: replaced self receiver 2024-07-07 16:19:58 +02:00
camoroso dd6404c786 t_scanner_test.go: replaced t.Log(fmtStringf()) with t.Logf() 2024-07-07 16:18:39 +02:00
camoroso 34874ef663 plugins.go: replaced stringsIndex()<0 with !strings.Contains() 2024-07-07 16:17:48 +02:00
camoroso 9bc8e8ca05 operator-sum.go: replaced for-loop with append() 2024-07-07 16:15:56 +02:00
camoroso 7f367cfc49 parser.go: renamed self receiver 2024-07-07 16:14:52 +02:00
camoroso fe999acf2c operator-index.go: removed unused parameter from the function verifyKey() 2024-07-07 16:14:04 +02:00
camoroso 2ed1a1842b operand-iterator.go: commented out an unused function and replaced self receiver 2024-07-07 16:10:43 +02:00
camoroso bb9493d0cc list-type.go: commented out an unused fuction 2024-07-07 16:08:45 +02:00
camoroso f279bf163e import-utils.go: commented out unused fuctions 2024-07-07 15:59:23 +02:00
camoroso 6834d9f47b function.go: removed useless param != nil check 2024-07-07 15:58:29 +02:00
camoroso 8051faa2bf fraction-type.go: use of strings.TrimSuffix() in place of check suffix and slice 2024-07-07 15:57:17 +02:00
camoroso f30e687a79 changed the file name comment 2024-07-07 15:55:51 +02:00
camoroso 2b6e46576b byte-slider.go: renamed function receiver from self to slider 2024-07-07 15:54:26 +02:00
camoroso dc06c03112 builtin-string.go: removed useless err == nil check 2024-07-07 15:53:29 +02:00
camoroso e8f5d3e445 builtin-base.go: unused function iteratorFunc() commented out 2024-07-07 15:52:16 +02:00
camoroso 76ce0945f7 context.go splitted in two files: expr-context.go and expr-function-go.
Expr interface moved from ast.go to the new file expr.go
2024-07-07 15:51:29 +02:00
camoroso 340b99bad7 list-type.go: use of copy() for copying lists 2024-07-07 07:34:58 +02:00
camoroso 93dac956fb t_parser_test.go: more tests on ??, ?= and ?! operators 2024-07-06 17:01:23 +02:00
camoroso 307027d23d t_parser_test.go: changed all error values to string values 2024-07-06 16:47:00 +02:00
camoroso 896844e772 the common test framework now supports error, string and nil as value of the wantErr field 2024-07-06 16:43:13 +02:00
camoroso 9fc20077a1 operator-include.go: the include operator did not count the number of files included when its argument was a single string 2024-07-06 16:08:58 +02:00
camoroso d2a4adebdd dataCursor implements Typer interface 2024-07-06 16:05:54 +02:00
camoroso fd8e32e12b new operator "?!" (alternate value) 2024-07-06 05:54:53 +02:00
camoroso 1e62a51c15 t_fractions_test.go: added a test on the string() function 2024-07-06 04:56:00 +02:00
camoroso 0170baa0f5 iter-list.go: implemented the Typer interface 2024-07-06 04:54:42 +02:00
camoroso fe5c8e9619 tests improved 2024-06-26 04:29:40 +02:00
camoroso 7164e8816c operator-bool.go: error messages improved 2024-06-26 04:28:53 +02:00
camoroso e0f3b028fc builtin-string.go: replaced ToInt() with ToGoInt() 2024-06-26 04:28:07 +02:00
camoroso 522b5983bd builtin-base.go: string() function added 2024-06-26 04:27:14 +02:00
camoroso f1e2163277 replaced ToInt() with ToGoInt() 2024-06-26 04:25:49 +02:00
camoroso bbdf498cf3 function.go: commented out some unused error functions 2024-06-26 04:23:48 +02:00
camoroso f75c991ed2 common-errors.go: commented out some unused error functions 2024-06-26 04:23:14 +02:00
camoroso a7836143cc utils.go: ToInt() renamed as ToGoInt() 2024-06-26 04:22:29 +02:00
camoroso ba9b9cb28f a lot oh changes to the test framework and new test files t_builtin-fmt_test.go and t_plugin_test.go added 2024-06-25 10:59:03 +02:00
camoroso ef1baa11a8 builtin-fmt.go: print() and println() can write data to a generic Writer. They fetch the writer from the control variable '_stdout'.
If _stdout is nil, they write to os.Stdout
2024-06-25 10:55:54 +02:00
camoroso cfddbd60b9 control.go: use of UnsafeSetVar() in place of SetVar(). SetCtrl() added 2024-06-25 10:53:05 +02:00
camoroso 0760141caf utils.go: Removed CloneMap(). fromGenericAny(): check v against nil 2024-06-25 10:48:31 +02:00
camoroso b9e780e659 ExprContext: removed Merge() method 2024-06-25 10:45:54 +02:00
camoroso e41ddc664c Improved closure context persistence. Now it is possibile to define counters like this func(base){func(){@base=base+1}} 2024-06-24 07:20:17 +02:00
camoroso 3b641ac793 Doc: little changes 2024-06-21 09:06:25 +02:00
camoroso a1a62b6794 Doc: little changes 2024-06-20 07:10:59 +02:00
camoroso a18d92534f Doc: little changes 2024-06-19 22:51:37 +02:00
camoroso ec0963e26f Doc: little changes 2024-06-19 22:46:35 +02:00
camoroso be992131b1 Doc: little changes 2024-06-19 22:25:38 +02:00
camoroso 0e3960321f Doc: little changes 2024-06-19 22:22:10 +02:00
camoroso 61d34fef7d Doc: little changes 2024-06-19 22:20:28 +02:00
camoroso 581f1585e6 Doc: embedded images 2024-06-19 22:04:13 +02:00
107 changed files with 4575 additions and 2105 deletions
+37 -38
View File
@@ -8,11 +8,6 @@ import (
"strings"
)
type Expr interface {
Eval(ctx ExprContext) (result any, err error)
String() string
}
//-------- ast
type ast struct {
@@ -24,65 +19,69 @@ func NewAst() *ast {
return &ast{}
}
func (self *ast) ToForest() {
if self.root != nil {
if self.forest == nil {
self.forest = make([]*term, 0)
func (expr *ast) TypeName() string {
return "Expression"
}
self.forest = append(self.forest, self.root)
self.root = nil
func (expr *ast) ToForest() {
if expr.root != nil {
if expr.forest == nil {
expr.forest = make([]*term, 0)
}
expr.forest = append(expr.forest, expr.root)
expr.root = nil
}
}
func (self *ast) String() string {
func (expr *ast) String() string {
var sb strings.Builder
if self.root == nil {
if expr.root == nil {
sb.WriteString("(nil)")
} else {
self.root.toString(&sb)
expr.root.toString(&sb)
}
return sb.String()
}
func (self *ast) addTokens(tokens ...*Token) (err error) {
func (expr *ast) addTokens(tokens ...*Token) (err error) {
for _, tk := range tokens {
if err = self.addToken(tk); err != nil {
if _, err = expr.addToken(tk); err != nil {
break
}
}
return
}
func (self *ast) addToken(tk *Token) (err error) {
_, err = self.addToken2(tk)
return
}
// func (expr *ast) addToken(tk *Token) (err error) {
// _, err = expr.addToken2(tk)
// 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 {
err = self.addTerm(t)
err = expr.addTerm(t)
} else {
err = tk.Errorf("unexpected token %q", tk.String())
}
return
}
func (self *ast) addTerm(node *term) (err error) {
if self.root == nil {
self.root = node
func (expr *ast) addTerm(node *term) (err error) {
if expr.root == nil {
expr.root = node
} else {
self.root, err = self.insert(self.root, node)
expr.root, err = expr.insert(expr.root, node)
}
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() {
root = tree
if tree.isComplete() {
var subRoot *term
last := tree.removeLastChild()
if subRoot, err = self.insert(last, node); err == nil {
if subRoot, err = expr.insert(last, node); err == nil {
subRoot.setParent(tree)
}
} else {
@@ -97,20 +96,20 @@ func (self *ast) insert(tree, node *term) (root *term, err error) {
return
}
func (self *ast) Finish() {
if self.root == nil && self.forest != nil && len(self.forest) >= 1 {
self.root = self.forest[len(self.forest)-1]
self.forest = self.forest[0 : len(self.forest)-1]
func (expr *ast) Finish() {
if expr.root == nil && expr.forest != nil && len(expr.forest) >= 1 {
expr.root = expr.forest[len(expr.forest)-1]
expr.forest = expr.forest[0 : len(expr.forest)-1]
}
}
func (self *ast) Eval(ctx ExprContext) (result any, err error) {
self.Finish()
func (expr *ast) Eval(ctx ExprContext) (result any, err error) {
expr.Finish()
if self.root != nil {
if expr.root != nil {
// initDefaultVars(ctx)
if self.forest != nil {
for _, root := range self.forest {
if expr.forest != nil {
for _, root := range expr.forest {
if result, err = root.compute(ctx); err == nil {
ctx.UnsafeSetVar(ControlLastResult, result)
} else {
@@ -120,7 +119,7 @@ func (self *ast) Eval(ctx ExprContext) (result any, err error) {
}
}
if err == nil {
if result, err = self.root.compute(ctx); err == nil {
if result, err = expr.root.compute(ctx); err == nil {
ctx.UnsafeSetVar(ControlLastResult, result)
}
}
+27 -26
View File
@@ -12,47 +12,48 @@ type exprFunctor struct {
defCtx ExprContext
}
// func newExprFunctor(e Expr, params []string, ctx ExprContext) *exprFunctor {
// return &exprFunctor{expr: e, params: params, defCtx: ctx}
// }
func (functor *exprFunctor) GetParams() (params []ExprFuncParam) {
return functor.params
}
func newExprFunctor(e Expr, params []ExprFuncParam, ctx ExprContext) *exprFunctor {
return &exprFunctor{expr: e, params: params, defCtx: ctx}
var defCtx ExprContext
if ctx != nil {
defCtx = ctx
}
return &exprFunctor{expr: e, params: params, defCtx: defCtx}
}
func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
if functor.defCtx != nil {
ctx.Merge(functor.defCtx)
func (functor *exprFunctor) TypeName() string {
return "ExprFunctor"
}
for i, p := range functor.params {
if i < len(args) {
arg := args[i]
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 {
// ctx.RegisterFunc(p, functor, 0, -1)
paramSpecs := funcArg.GetParams()
ctx.RegisterFunc(p.Name(), funcArg, TypeAny, paramSpecs)
} else {
ctx.UnsafeSetVar(p.Name(), arg)
}
} else {
ctx.UnsafeSetVar(p.Name(), nil)
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
}
// func CallExprFunction(parentCtx ExprContext, funcName string, params ...any) (v any, err error) {
// ctx := cloneContext(parentCtx)
// ctx.SetParent(parentCtx)
// if err == nil {
// if err = checkFunctionCall(ctx, funcName, &params); err == nil {
// if v, err = ctx.Call(funcName, params); err == nil {
// exportObjects(parentCtx, ctx)
// }
// }
// }
// return
// }
+5 -1
View File
@@ -14,6 +14,10 @@ func NewGolangFunctor(f FuncTemplate) *golangFunctor {
return &golangFunctor{f: f}
}
func (functor *golangFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
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)
}
+93 -32
View File
@@ -5,57 +5,63 @@
package expr
import (
"fmt"
"math"
"strconv"
"strings"
)
func isNilFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = args[0] == nil
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 []any) (result any, err error) {
result = IsInteger(args[0])
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 []any) (result any, err error) {
result = IsFloat(args[0])
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 []any) (result any, err error) {
result = IsBool(args[0])
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 []any) (result any, err error) {
result = IsString(args[0])
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 []any) (result any, err error) {
result = IsFract(args[0])
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 []any) (result any, err error) {
result = IsRational(args[0])
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 []any) (result any, err error) {
result = IsList(args[0])
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 []any) (result any, err error) {
result = IsDict(args[0])
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 []any) (result any, err error) {
switch v := args[0].(type) {
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:
@@ -72,8 +78,8 @@ func boolFunc(ctx ExprContext, name string, args []any) (result any, err error)
return
}
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
switch v := args[0].(type) {
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:
@@ -95,8 +101,8 @@ func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
return
}
func decFunc(ctx ExprContext, name string, args []any) (result any, err error) {
switch v := args[0].(type) {
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:
@@ -120,18 +126,44 @@ func decFunc(ctx ExprContext, name string, args []any) (result any, err error) {
return
}
func fractFunc(ctx ExprContext, name string, args []any) (result any, err error) {
switch v := args[0].(type) {
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
if len(args) > 1 {
var ok bool
if den, ok = args[1].(int64); !ok {
err = ErrExpectedGot(name, "integer", args[1])
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)
}
@@ -153,10 +185,34 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
return
}
func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err error) {
// 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),
@@ -175,9 +231,14 @@ func ImportBuiltinsFuncs(ctx ExprContext) {
ctx.RegisterFunc("bool", NewGolangFunctor(boolFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("int", NewGolangFunctor(intFunc), TypeInt, anyParams)
ctx.RegisterFunc("dec", NewGolangFunctor(decFunc), TypeFloat, anyParams)
ctx.RegisterFunc("string", NewGolangFunctor(stringFunc), TypeString, anyParams)
ctx.RegisterFunc("fract", NewGolangFunctor(fractFunc), TypeFraction, []ExprFuncParam{
NewFuncParam(ParamValue),
NewFuncParamFlagDef("denominator", PfDefault, 1),
NewFuncParamFlagDef(ParamDenominator, PfDefault, int64(1)),
})
ctx.RegisterFunc("eval", NewGolangFunctor(evalFunc), TypeAny, []ExprFuncParam{
NewFuncParam(ParamSource),
})
}
+30 -9
View File
@@ -4,21 +4,42 @@
// builtin-fmt.go
package expr
import "fmt"
import (
"fmt"
"io"
"os"
)
func printFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var n int
if n, err = fmt.Print(args...); err == nil {
result = int64(n)
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 []any) (result any, err error) {
var n int
if n, err = fmt.Println(args...); err == nil {
result = int64(n)
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
}
+8 -5
View File
@@ -9,18 +9,21 @@ import (
"os"
)
func importFunc(ctx ExprContext, name string, args []any) (result any, err error) {
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 []any) (result any, err error) {
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 []any) (result any, err error) {
func importGeneral(ctx ExprContext, name string, args map[string]any) (result any, err error) {
dirList := buildSearchDirList("sources", ENV_EXPR_SOURCE_PATH)
result, err = doImport(ctx, name, dirList, NewArrayIterator(args))
if v, exists := args[ParamFilepath]; exists && v != nil {
argv := v.([]any)
result, err = doImport(ctx, name, dirList, NewArrayIterator(argv))
}
return
}
@@ -41,7 +44,7 @@ func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (resu
var expr *ast
scanner := NewScanner(file, DefaultTranslations())
parser := NewParser()
if expr, err = parser.parseGeneral(scanner, true, true); err == nil {
if expr, err = parser.parseGeneral(scanner, allowMultiExpr|allowVarRef, SymEos); err == nil {
result, err = expr.Eval(ctx)
}
if err != nil {
+104
View 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")
}
+12 -10
View File
@@ -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 {
break
}
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(cleanName) {
if _, err = extIter.CallOperation(cleanName, nil); err != nil {
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(CleanName) {
if _, err = extIter.CallOperation(CleanName, nil); err != nil {
return
}
}
@@ -86,8 +86,9 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
return
}
func addFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result, err = doAdd(ctx, name, NewArrayIterator(args), 0, -1)
func addFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
argv := args[ParamValue].([]any)
result, err = doAdd(ctx, name, NewArrayIterator(argv), 0, -1)
return
}
@@ -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 {
break
}
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(cleanName) {
if _, err = extIter.CallOperation(cleanName, nil); err != nil {
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(CleanName) {
if _, err = extIter.CallOperation(CleanName, nil); err != nil {
return
}
}
@@ -161,17 +162,18 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
return
}
func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result, err = doMul(ctx, name, NewArrayIterator(args), 0, -1)
func mulFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
argv := args[ParamValue].([]any)
result, err = doMul(ctx, name, NewArrayIterator(argv), 0, -1)
return
}
func ImportMathFuncs(ctx ExprContext) {
ctx.RegisterFunc("add", &golangFunctor{f: addFunc}, TypeNumber, []ExprFuncParam{
ctx.RegisterFunc("add", NewGolangFunctor(addFunc), TypeNumber, []ExprFuncParam{
NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, int64(0)),
})
ctx.RegisterFunc("mul", &golangFunctor{f: mulFunc}, TypeNumber, []ExprFuncParam{
ctx.RegisterFunc("mul", NewGolangFunctor(mulFunc), TypeNumber, []ExprFuncParam{
NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, int64(1)),
})
}
+36 -29
View File
@@ -11,6 +11,10 @@ import (
"os"
)
const (
osLimitCh = "limitCh"
)
type osHandle interface {
getFile() *os.File
}
@@ -61,8 +65,8 @@ func errInvalidFileHandle(funcName string, v any) error {
}
}
func createFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if filePath, ok := args[0].(string); ok && len(filePath) > 0 {
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)}
@@ -73,8 +77,8 @@ func createFileFunc(ctx ExprContext, name string, args []any) (result any, err e
return
}
func openFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if filePath, ok := args[0].(string); ok && len(filePath) > 0 {
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)}
@@ -85,8 +89,8 @@ func openFileFunc(ctx ExprContext, name string, args []any) (result any, err err
return
}
func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if filePath, ok := args[0].(string); ok && len(filePath) > 0 {
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)}
@@ -97,13 +101,13 @@ func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err e
return
}
func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
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[0].(osHandle); !ok {
invalidFileHandle = args[0]
if handle, ok = args[ParamHandle].(osHandle); !ok {
invalidFileHandle = args[ParamHandle]
}
if handle != nil {
@@ -124,18 +128,21 @@ func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err er
return
}
func fileWriteTextFunc(ctx ExprContext, name string, args []any) (result any, err error) {
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[0].(osHandle); !ok {
invalidFileHandle = args[0]
if handle, ok = args[ParamHandle].(osHandle); !ok {
invalidFileHandle = args[ParamHandle]
}
if handle != nil {
if w, ok := handle.(*osWriter); ok {
result, err = fmt.Fprint(w.writer, args[1:]...)
if v, exists := args[ParamItem]; exists {
argv := v.([]any)
result, err = fmt.Fprint(w.writer, argv...)
}
} else {
invalidFileHandle = handle
}
@@ -147,21 +154,21 @@ func fileWriteTextFunc(ctx ExprContext, name string, args []any) (result any, er
return
}
func fileReadTextFunc(ctx ExprContext, name string, args []any) (result any, err error) {
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[0].(osHandle); !ok || args[0] == nil {
invalidFileHandle = args[0]
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[1].(string); ok && len(s) > 0 {
if s, ok := args[osLimitCh].(string); ok && len(s) > 0 {
limit = s[0]
}
@@ -187,14 +194,14 @@ func fileReadTextFunc(ctx ExprContext, name string, args []any) (result any, err
return
}
func fileReadTextAllFunc(ctx ExprContext, name string, args []any) (result any, err error) {
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[0].(osHandle); !ok || args[0] == nil {
invalidFileHandle = args[0]
if handle, ok = args[ParamHandle].(osHandle); !ok || args[ParamHandle] == nil {
invalidFileHandle = args[ParamHandle]
}
if handle != nil {
@@ -214,34 +221,34 @@ func fileReadTextAllFunc(ctx ExprContext, name string, args []any) (result any,
}
func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("fileOpen", NewGolangFunctor(openFileFunc), TypeHandle, []ExprFuncParam{
ctx.RegisterFunc("fileOpen", NewGolangFunctor(openFileFunc), TypeFileHandle, []ExprFuncParam{
NewFuncParam(ParamFilepath),
})
ctx.RegisterFunc("fileAppend", NewGolangFunctor(appendFileFunc), TypeHandle, []ExprFuncParam{
ctx.RegisterFunc("fileAppend", NewGolangFunctor(appendFileFunc), TypeFileHandle, []ExprFuncParam{
NewFuncParam(ParamFilepath),
})
ctx.RegisterFunc("fileCreate", NewGolangFunctor(createFileFunc), TypeHandle, []ExprFuncParam{
ctx.RegisterFunc("fileCreate", NewGolangFunctor(createFileFunc), TypeFileHandle, []ExprFuncParam{
NewFuncParam(ParamFilepath),
})
ctx.RegisterFunc("fileClose", NewGolangFunctor(closeFileFunc), TypeBoolean, []ExprFuncParam{
NewFuncParam(TypeHandle),
NewFuncParam(ParamHandle),
})
ctx.RegisterFunc("fileWriteText", NewGolangFunctor(fileWriteTextFunc), TypeInt, []ExprFuncParam{
NewFuncParam(TypeHandle),
NewFuncParamFlagDef(TypeItem, PfDefault|PfRepeat, ""),
NewFuncParam(ParamHandle),
NewFuncParamFlagDef(ParamItem, PfDefault|PfRepeat, ""),
})
ctx.RegisterFunc("fileReadText", NewGolangFunctor(fileReadTextFunc), TypeString, []ExprFuncParam{
NewFuncParam(TypeHandle),
NewFuncParamFlagDef("limitCh", PfDefault, "\n"),
NewFuncParam(ParamHandle),
NewFuncParamFlagDef(osLimitCh, PfDefault, "\n"),
})
ctx.RegisterFunc("fileReadTextAll", NewGolangFunctor(fileReadTextAllFunc), TypeString, []ExprFuncParam{
NewFuncParam(TypeHandle),
NewFuncParam(ParamHandle),
})
}
+65 -43
View File
@@ -10,6 +10,10 @@ import (
"strings"
)
const (
strParamOther = "other"
)
// --- Start of function definitions
func doJoinStr(funcName string, sep string, it Iterator) (result any, err error) {
var sb strings.Builder
@@ -25,52 +29,52 @@ func doJoinStr(funcName string, sep string, it Iterator) (result any, err error)
return
}
}
if err == nil || err == io.EOF {
if err == io.EOF {
err = nil
result = sb.String()
}
return
}
func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
// if len(args) < 1 {
// return nil, errMissingRequiredParameter(name, paramSeparator)
// }
if sep, ok := args[0].(string); ok {
if len(args) == 1 {
result = ""
} else if len(args) == 2 {
if ls, ok := args[1].(*ListType); ok {
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 := args[1].(Iterator); ok {
} 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, ParamParts, args[1])
err = ErrInvalidParameterValue(name, ParamItem, v)
}
} else {
result, err = doJoinStr(name, sep, NewArrayIterator(args[1:]))
result, err = doJoinStr(name, sep, NewArrayIterator(argv))
}
}
} else {
err = ErrWrongParamType(name, ParamSeparator, TypeString, args[0])
err = ErrWrongParamType(name, ParamSeparator, TypeString, args[ParamSeparator])
}
return
}
func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
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[0].(string); !ok {
return nil, ErrWrongParamType(name, ParamSource, TypeString, args[0])
if source, ok = args[ParamSource].(string); !ok {
return nil, ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
}
if start, err = ToInt(args[1], name+"()"); err != nil {
if start, err = ToGoInt(args[ParamStart], name+"()"); err != nil {
return
}
if count, err = ToInt(args[2], name+"()"); err != nil {
if count, err = ToGoInt(args[ParamCount], name+"()"); err != nil {
return
}
@@ -86,81 +90,99 @@ func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error
return
}
func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
func trimStrFunc(ctx ExprContext, name string, args map[string]any) (result any, err error) {
var source string
var ok bool
if source, ok = args[0].(string); !ok {
return nil, ErrWrongParamType(name, ParamSource, TypeString, args[0])
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 []any) (result any, err error) {
var source string
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[0].(string); !ok {
return result, ErrWrongParamType(name, ParamSource, TypeString, args[0])
if source, ok = args[ParamSource].(string); !ok {
return result, ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
}
for i, targetSpec := range args[1:] {
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 %T, expected string", i+1, targetSpec)
err = fmt.Errorf("target item nr %d is %s, string expected", i+1, TypeName(targetSpec))
break
}
}
}
return
}
func endsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var source string
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[0].(string); !ok {
return result, ErrWrongParamType(name, ParamSource, TypeString, args[0])
if source, ok = args[ParamSource].(string); !ok {
return result, ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
}
for i, targetSpec := range args[1:] {
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 %T, expected string", i+1, targetSpec)
err = fmt.Errorf("target item nr %d is %s, string expected", i+1, TypeName(targetSpec))
break
}
}
}
return
}
func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err error) {
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[0].(string); !ok {
return result, ErrWrongParamType(name, ParamSource, TypeString, args[0])
if source, ok = args[ParamSource].(string); !ok {
return result, ErrWrongParamType(name, ParamSource, TypeString, args[ParamSource])
}
if sep, ok = args[1].(string); !ok {
return nil, fmt.Errorf("separator param must be string, got %T (%v)", args[1], args[1])
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[2].(int64); ok { // TODO replace type assertion with toInt()
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 %T (%v)", args[2], args[2])
return nil, fmt.Errorf("part count must be integer, got %s (%v)", TypeName(args[ParamCount]), args[ParamCount])
}
if count > 0 {
@@ -206,13 +228,13 @@ func ImportStringFuncs(ctx ExprContext) {
ctx.RegisterFunc("strStartsWith", NewGolangFunctor(startsWithStrFunc), TypeBoolean, []ExprFuncParam{
NewFuncParam(ParamSource),
NewFuncParam(ParamPrefix),
NewFuncParamFlag("other "+ParamPrefix, PfRepeat),
NewFuncParamFlag(strParamOther, PfRepeat),
})
ctx.RegisterFunc("strEndsWith", NewGolangFunctor(endsWithStrFunc), TypeBoolean, []ExprFuncParam{
NewFuncParam(ParamSource),
NewFuncParam(ParamSuffix),
NewFuncParamFlag("other "+ParamSuffix, PfRepeat),
NewFuncParamFlag(strParamOther, PfRepeat),
})
}
+9 -9
View File
@@ -18,17 +18,17 @@ func NewByteSlider(size int) *ByteSlider {
}
}
func (self *ByteSlider) PushEnd(b byte) {
if self.length == cap(self.buf) {
self.length--
for i := 0; i < self.length; i++ {
self.buf[i] = self.buf[i+1]
func (slider *ByteSlider) PushEnd(b byte) {
if slider.length == cap(slider.buf) {
slider.length--
for i := 0; i < slider.length; i++ {
slider.buf[i] = slider.buf[i+1]
}
}
self.buf[self.length] = b
self.length++
slider.buf[slider.length] = b
slider.length++
}
func (self *ByteSlider) Equal(target []byte) bool {
return target != nil && bytes.Equal(self.buf, target)
func (slider *ByteSlider) Equal(target []byte) bool {
return target != nil && bytes.Equal(slider.buf, target)
}
+41 -9
View File
@@ -6,8 +6,13 @@ package expr
import (
"fmt"
"strings"
)
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 {
err = fmt.Errorf("%s(): too few params -- expected %d or more, got %d", funcName, minArgs, argCount)
@@ -17,8 +22,8 @@ func ErrTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error
return
}
func ErrTooMuchParams(funcName string, maxArgs, argCount int) (err error) {
err = fmt.Errorf("%s(): too much params -- expected %d, got %d", funcName, maxArgs, argCount)
func ErrTooManyParams(funcName string, maxArgs, argCount int) (err error) {
err = fmt.Errorf("%s(): too many params -- expected %d, got %d", funcName, maxArgs, argCount)
return
}
@@ -36,20 +41,47 @@ func ErrFuncDivisionByZero(funcName string) error {
return fmt.Errorf("%s(): division by zero", funcName)
}
func ErrDivisionByZero() error {
return fmt.Errorf("division by zero")
}
// func ErrDivisionByZero() error {
// return fmt.Errorf("division by zero")
// }
// --- Parameter errors
func ErrMissingRequiredParameter(funcName, paramName string) error {
return fmt.Errorf("%s(): missing required parameter %q", funcName, paramName)
}
// func ErrMissingRequiredParameter(funcName, paramName string) error {
// return fmt.Errorf("%s(): missing required parameter %q", funcName, paramName)
// }
func ErrInvalidParameterValue(funcName, paramName string, paramValue any) error {
return fmt.Errorf("%s(): invalid value %s (%v) for parameter %q", funcName, TypeName(paramValue), paramValue, paramName)
}
func undefArticle(s string) (article string) {
if len(s) > 0 && strings.Contains("aeiou", s[0:1]) {
article = "an"
} else {
article = "a"
}
return
}
func prependUndefArticle(s string) (result string) {
return undefArticle(s) + " " + s
}
func ErrWrongParamType(funcName, paramName, paramType string, paramValue any) error {
return fmt.Errorf("%s(): the %q parameter must be a %s, got a %s (%v)", funcName, paramName, paramType, TypeName(paramValue), paramValue)
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
View File
@@ -5,8 +5,10 @@
package expr
const (
ParamArgs = "args"
ParamCount = "count"
ParamItem = "item"
ParamIndex = "index"
ParamParts = "parts"
ParamSeparator = "separator"
ParamSource = "source"
@@ -19,6 +21,9 @@ const (
ParamEllipsis = "..."
ParamFilepath = "filepath"
ParamDirpath = "dirpath"
ParamHandle = "handle"
ParamResource = "resource"
ParamIterator = "iterator"
)
// to be moved in its own source file
+3 -1
View File
@@ -6,15 +6,17 @@ package expr
const (
TypeAny = "any"
TypeNil = "nil"
TypeBoolean = "boolean"
TypeFloat = "float"
TypeFraction = "fraction"
TypeHandle = "handle"
TypeFileHandle = "file-handle"
TypeInt = "integer"
TypeItem = "item"
TypeNumber = "number"
TypePair = "pair"
TypeString = "string"
TypeDict = "dict"
TypeListOf = "list-of-"
TypeListOfStrings = "list-of-strings"
)
+1 -1
View File
@@ -29,7 +29,7 @@ func exportObjects(destCtx, sourceCtx ExprContext) {
exportAll := CtrlIsEnabled(sourceCtx, control_export_all)
// fmt.Printf("Exporting from sourceCtx [%p] to destCtx [%p] -- exportAll=%t\n", sourceCtx, destCtx, exportAll)
// Export variables
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return (exportAll || name[0] == '@') && !(name[0] == '_') }) {
// fmt.Printf("\tExporting %q\n", refName)
refValue, _ := sourceCtx.GetVar(refName)
exportVar(destCtx, refName, refValue)
-53
View File
@@ -1,53 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// context.go
package expr
// ---- Functor interface
type Functor interface {
Invoke(ctx ExprContext, name string, args []any) (result any, err error)
SetFunc(info ExprFunc)
GetFunc() ExprFunc
GetParams() []ExprFuncParam
}
// ---- Function Param Info
type ExprFuncParam interface {
Name() string
Type() string
IsDefault() bool
IsOptional() bool
IsRepeat() bool
DefaultValue() any
}
// ---- Function Info
type ExprFunc interface {
Formatter
Name() string
MinArgs() int
MaxArgs() int
Functor() Functor
Params() []ExprFuncParam
ReturnType() string
}
// ----Expression Context
type ExprContext interface {
Clone() ExprContext
Merge(ctx ExprContext)
SetParent(ctx ExprContext)
GetParent() (ctx ExprContext)
GetVar(varName string) (value any, exists bool)
GetLast() any
SetVar(varName string, value any)
UnsafeSetVar(varName string, value any)
EnumVars(func(name string) (accept bool)) (varNames []string)
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
GetFuncInfo(name string) (item ExprFunc, exists bool)
Call(name string, args []any) (result any, err error)
// RegisterFunc(name string, f Functor, minArgs, maxArgs int)
RegisterFuncInfo(info ExprFunc)
RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) error
}
+10 -3
View File
@@ -11,6 +11,7 @@ const (
ControlBoolShortcut = "_bool_shortcut"
ControlSearchPath = "_search_path"
ControlParentContext = "_parent_context"
ControlStdout = "_stdout"
)
// Other control variables
@@ -23,11 +24,17 @@ const (
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) {
if _, exists := ctx.GetVar(ControlPreset); exists {
return
}
ctx.SetVar(ControlPreset, true)
ctx.SetVar(ControlBoolShortcut, true)
ctx.SetVar(ControlSearchPath, init_search_path)
ctx.UnsafeSetVar(ControlPreset, true)
ctx.UnsafeSetVar(ControlBoolShortcut, true)
ctx.UnsafeSetVar(ControlSearchPath, init_search_path)
}
+203 -52
View File
@@ -5,30 +5,51 @@
package expr
import (
"errors"
"io"
"slices"
)
type dataCursor struct {
ds map[string]Functor
ctx ExprContext
initState bool // true if no item has produced yet (this replace di initial Next() call in the contructor)
// cursorValid bool // true if resource is nil or if clean has not yet been called
index int
count int
current any
lastErr error
resource any
nextFunc Functor
cleanFunc Functor
resetFunc Functor
currentFunc 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{
ds: ds,
initState: true,
// 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
}
func (dc *dataCursor) Context() ExprContext {
return dc.ctx
}
func (dc *dataCursor) TypeName() string {
return "DataCursor"
}
// func mapToString(m map[string]Functor) string {
// var sb strings.Builder
// sb.WriteByte('{')
@@ -58,7 +79,7 @@ func (dc *dataCursor) String() string {
}
func (dc *dataCursor) HasOperation(name string) (exists bool) {
exists = name == indexName
exists = slices.Contains([]string{CleanName, ResetName, CurrentName, IndexName}, name)
if !exists {
f, ok := dc.ds[name]
exists = ok && isFunctor(f)
@@ -66,89 +87,219 @@ func (dc *dataCursor) HasOperation(name string) (exists bool) {
return
}
func (dc *dataCursor) CallOperation(name string, args []any) (value any, err error) {
if name == indexName {
func (dc *dataCursor) CallOperation(name string, args map[string]any) (value any, err error) {
if name == IndexName {
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) {
if functor == dc.cleanFunc {
value, err = dc.Clean()
} else if functor == dc.resetFunc {
value, err = dc.Reset()
} else {
ctx := cloneContext(dc.ctx)
value, err = functor.Invoke(ctx, name, []any{})
value, err = functor.InvokeNamed(ctx, name, args)
exportObjects(dc.ctx, ctx)
}
} else {
err = errNoOperation(name)
}
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.resource != nil {
ctx := cloneContext(dc.ctx)
if _, err = dc.resetFunc.Invoke(ctx, resetName, []any{dc.resource}); err == nil {
dc.index = -1
}
actualParams := bindActualParams(dc.resetFunc, []any{dc.resource})
_, err = dc.resetFunc.InvokeNamed(ctx, ResetName, actualParams)
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
}
func (dc *dataCursor) Clean() (success bool, err error) {
func (dc *dataCursor) Clean() (err error) {
if dc.cleanFunc != nil {
if dc.resource != nil {
ctx := cloneContext(dc.ctx)
_, err = dc.cleanFunc.Invoke(ctx, cleanName, []any{dc.resource})
dc.resource = nil
actualParams := bindActualParams(dc.cleanFunc, []any{dc.resource})
_, err = dc.cleanFunc.InvokeNamed(ctx, CleanName, actualParams)
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
}
// 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
ctx := cloneContext(dc.ctx)
if item, err = dc.currentFunc.Invoke(ctx, currentName, []any{}); err == nil && item == nil {
dc.init()
if dc.current != nil {
item = dc.current
} else {
err = io.EOF
}
exportObjects(dc.ctx, ctx)
return
}
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item
if dc.resource != nil {
func (dc *dataCursor) checkFilter(filter Functor, item any) (accepted bool, err error) {
var v any
var ok bool
ctx := cloneContext(dc.ctx)
// fmt.Printf("Entering Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil {
if item == nil {
err = io.EOF
} else {
dc.index++
actualParams := bindActualParams(filter, []any{item, dc.index})
if v, err = filter.InvokeNamed(ctx, FilterName, actualParams); err == nil && v != nil {
if accepted, ok = v.(bool); !ok {
accepted = true // NOTE: A non-boolean value that is not nil means the item has been accepted
}
}
// fmt.Printf("Exiting Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0))
exportObjects(dc.ctx, ctx)
// fmt.Printf("Outer-Ctx [%p]: %s\n", dc.ctx, CtxToString(dc.ctx, 0))
} else {
err = errInvalidDataSource()
}
return
}
func (dc *dataCursor) mapItem(mapper Functor, item any) (mappedItem any, err error) {
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
return dc.index - 1
}
func (dc *dataCursor) Count() int {
return dc.count
}
+15
View File
@@ -18,7 +18,22 @@ func MakeDict() (dict *DictType) {
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 wi a call to NewDict()
var d DictType
if dictAny != nil {
d = make(DictType, len(dictAny))
+476 -188
View File
@@ -9,6 +9,7 @@ Expressions calculator
:icons: font
:icon-set: fi
:numbered:
:data-uri:
//:table-caption: Tabella
//:figure-caption: Diagramma
:docinfo1:
@@ -19,10 +20,12 @@ Expressions calculator
:rouge-style: gruvbox
// :rouge-style: colorful
//:rouge-style: monokay
// Work around to manage double-column in back-tick quotes
:2c: ::
toc::[]
#TODO: Work in progress (last update on 2024/06/17, 16:31 a.m.)#
#TODO: Work in progress (last update on 2024/06/21, 05:40 a.m.)#
== Expr
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
@@ -55,11 +58,13 @@ The expression context is analogous to the stack-frame of other programming lang
Function contexts are created by cloning the calling context. More details on this topic are given later in this document.
_Expr_ creates and keeps a inner _global context_ where it stores imported functions, either from builtin or plugin modules. To perform calculations, the calling program must provide its own context; this is the _main context_. All calculations take place in this context. As mentioned eralier, when a function is called, a new context is created by cloning the calling context. The createt context can be called _function context_.
_Expr_ creates and keeps a inner _global context_ where it stores imported functions, either from builtin or plugin modules. To perform calculations, the user program must provide its own context; this is the _main context_. All calculations take place in this context. As mentioned eralier, when a function is called, a new context is created by cloning the calling context. The created context can be called _function context_.
Imported functions are registerd in the _global context_. When an expression first calls an imported function, that function is linked to the current context; this can be the _main context_ or a _function context_.
=== `dev-expr` test tool
Before we begin to describe the syntax of _Expr_, it is worth introducing _dev-expr_ because it will be used to show many examples of expressions.
`dev-expr` is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, in additon to the automatic verification test suite based on the Go test framework, `dev-expr` provided an important aid for quickly testing of new features during their development.
`dev-expr` can work as a _REPL_, _**R**ead-**E**xecute-**P**rint-**L**oop_, or it can process expression acquired from files or standard input.
@@ -74,20 +79,20 @@ Here are some examples of execution.
# Type 'exit' or Ctrl+D to quit the program.
[user]$ ./dev-expr
dev-expr -- Expressions calculator v1.10.0(build 14),2024/06/17 (celestino.amoroso@portale-stac.it)
Based on the Expr package v0.19.0
dev-expr -- Expressions calculator v1.12.0(build 1),2024/09/14 (celestino.amoroso@portale-stac.it)
Based on the Expr package v0.26.0
Type help to get the list of available commands
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
>>> help
--- REPL commands:
source -- Load a file as input
tty -- Enable/Disable ansi output <1>
base -- Set the integer output base: 2, 8, 10, or 16
exit -- Exit the program
help -- Show command list
ml -- Enable/Disable multi-line output
mods -- List builtin modules
output -- Enable/Disable printing expression results. Options 'on', 'off', 'status'
source -- Load a file as input
tty -- Enable/Disable ansi output <1>
--- Command line options:
-b <builtin> Import builtin modules.
@@ -122,22 +127,22 @@ dev-expr -- Expressions calculator v1.10.0(build 14),2024/06/17 (celestino.amoro
9.5
>>> 0xFD + 0b1 + 0o1 <1>
255
>>> 1|2 + 2|3 <2>
7|6
>>> 1:2 + 2:3 <2>
7:6
>>> ml <3>
>>> 1|2 + 2|3
>>> 1:2 + 2:3
7
-
6
>>> 4+2 but 5|2+0.5 <4>
>>> 4+2 but 5:2+0.5 <4>
3
>>> 4+2; 5|2+0.5 <5>
>>> 4+2; 5:2+0.5 <5>
3
>>>
----
<1> Number bases: 0x = _hexadecimal_, 0o = _octal_, 0b = _binary_.
<2> Fractions: _numerator_ | _denominator_.
<2> Fractions: _numerator_ : _denominator_.
<3> Activate multi-line output of fractions.
<4> But operator, see <<_but_operator>>.
<5> Multi-expression: the same result of the previous single expression but this it is obtained with two separated calculations.
@@ -150,9 +155,9 @@ _Expr_ supports three type of numbers:
. [blue]#Integers#
. [blue]#Floats#
. [blue]#Factions#
. [blue]#Fractions#
In mixed operations involving integers, fractions and floats, automatic type promotion to the largest type take place.
In mixed operations involving integers, fractions and floats, automatic type promotion to the largest type is performed.
==== Integers
__Expr__'s integers are a subset of the integer set. Internally they are stored as Golang _int64_ values.
@@ -178,11 +183,11 @@ Value range: *-9223372036854775808* to *9223372036854775807*
[cols="^1,^2,6,4"]
|===
| Symbol | Operation | Description | Examples
| [blue]`+` | _sum_ | Add two values | [blue]`-1 + 2` -> 1
| [blue]`-` | _subtraction_ | Subtract the right value from the left one | [blue]`3 - 1` -> 2
| [blue]`*` | _product_ | Multiply two values | [blue]`-1 * 2` -> -2
| [blue]`/` | _Division_ | Divide the left value by the right one^(*)^ | [blue]`-10 / 2` -> 5
| [blue]`%` | _Modulo_ | Remainder of the integer division | [blue]`5 % 2` -> 1
| [blue]`+` | _Sum_ | Add two values | [blue]`-1 + 2` -> _1_
| [blue]`-` | _Subtraction_ | Subtract the right value from the left one | [blue]`3 - 1` -> _2_
| [blue]`*` | _Product_ | Multiply two values | [blue]`-1 * 2` -> _-2_
| [blue]`/` | _Integer division_ | Divide the left value by the right one^(*)^ | [blue]`-11 / 2` -> _-5_
| [blue]`%` | _Modulo_ | Remainder of the integer division | [blue]`5 % 2` -> _1_
|===
^(*)^ See also the _float division_ [blue]`./` below.
@@ -201,15 +206,20 @@ _dec-seq_ = _see-integer-literal-syntax_
.Examples
`>>>` [blue]`1.0` +
[green]`1` +
[green]`1`
`>>>` [blue]`0.123` +
[green]`0.123` +
[green]`0.123`
`>>>` [blue]`4.5e+3` +
[green]`4500` +
[green]`4500`
`>>>` [blue]`4.5E-33` +
[green]`4.5e-33` +
[green]`4.5e-33`
`>>>` [blue]`4.5E-3` +
[green]`0.0045` +
[green]`0.0045`
`>>>` [blue]`4.5E10` +
[green]`4.5e+10`
@@ -218,19 +228,19 @@ _dec-seq_ = _see-integer-literal-syntax_
[cols="^1,^2,6,4"]
|===
| Symbol | Operation | Description | Examples
| [blue]`+` | _sum_ | Add two values | [blue]`4 + 0.5` -> 4.5
| [blue]`-` | _subtraction_ | Subtract the right value from the left one | [blue]`4 - 0.5` -> 3.5
| [blue]`*` | _product_ | Multiply two values | [blue]`4 * 0.5` -> 2.0
| [blue]`/` | _Division_ | Divide the left value by the right one | [blue]`1.0 / 2` -> 0.5
| [blue]`./`| _Float division_ | Force float division | [blue]`-1 ./ 2` -> -0.5
| [blue]`+` | _Sum_ | Add two values | [blue]`4 + 0.5` -> 4.5
| [blue]`-` | _Subtraction_ | Subtract the right value from the left one | [blue]`4 - 0.5` -> 3.5
| [blue]`*` | _Product_ | Multiply two values | [blue]`4 * 0.5` -> 2.0
| [blue]`/` | _Float division_ | Divide the left value by the right one | [blue]`1.0 / 2` -> 0.5
| [blue]`./`| _Forced float division_ | Force float division | [blue]`-1 ./ 2` -> -0.5
|===
==== Fractions
_Expr_ also supports fractions. Fraction literals are made with two integers separated by a vertical bar `|`.
_Expr_ also supports fractions. Fraction literals are made with two integers separated by a colon character `:`.
.Fraction literal syntax
====
*_fraction_* = [__sign__] (_num-den-spec_ | _float-spec_) +
*_fraction_* = [__sign__] (_num-den-spec_ "**:**" _float-spec_) +
_sign_ = "**+**" | "**-**" +
_num-den-spec_ = _digit-seq_ "**|**" _digit-seq_ +
_float-spec_ = _dec-seq_ "**.**" [_dec-seq_] "**(**" _dec-seq_ "**)**" +
@@ -239,36 +249,44 @@ _digit-seq_ = _see-integer-literal-syntax_
====
.Examples
// [source,go]
// ----
`>>>` [blue]`1 | 2` +
[green]`1|2` +
`>>>` [blue]`4|6` [gray]_// Fractions are always reduced to their lowest terms_ +
[green]`2|3` +
`>>>` [blue]`1|2 + 2|3` +
[green]`7|6` +
`>>>` [blue]`1|2 * 2|3` +
[green]`1|3` +
`>>>` [blue]`1|2 / 1|3` +
[green]`3|2` +
`>>>` [blue]`1|2 ./ 1|3` [gray]_// Force decimal division_ +
[green]`1.5` +
`>>>` [blue]`-1|2` +
[green]`-1|2` +
`>>>` [blue]`1|-2` [gray]_// Invalid sign specification_ +
[red]_Eval Error: [1:3] infix operator "|" requires two non-nil operands, got 1_ +
`>>>` [blue]`1|(-2)` +
[green]`-1|2`
// ----
`>>>` [blue]`1 : 2` +
[green]`1:2`
`>>>` [blue]`4:6` [gray]_// Fractions are always reduced to their lowest terms_ +
[green]`2:3`
`>>>` [blue]`1:2 + 2:3` +
[green]`7:6`
`>>>` [blue]`1:2 * 2:3` +
[green]`1:3`
`>>>` [blue]`1:2 / 1:3` +
[green]`3:2`
`>>>` [blue]`1:2 ./ 1:3` [gray]_// Force decimal division_ +
[green]`1.5`
`>>>` [blue]`-1:2` +
[green]`-1:2`
`>>>` [blue]`1:-2` [gray]_// Invalid sign specification_ +
[red]_Eval Error: [1:3] infix operator ":" requires two non-nil operands, got 1_
`>>>` [blue]`1:(-2)` +
[green]`-1:2`
Fractions can be used together with integers and floats in expressions.
.Examples
`>>>` [blue]`1|2 + 5` +
[green]`11|2` +
`>>>` [blue]`4 - 1|2` +
[green]`7|2` +
`>>>` [blue]`1.0 + 1|2` +
`>>>` [blue]`1:2 + 5` +
[green]`11:2`
`>>>` [blue]`4 - 1:2` +
[green]`7:2`
`>>>` [blue]`1.0 + 1:2` +
[green]`1.5`
@@ -278,16 +296,19 @@ Strings are character sequences enclosed between two double quote [blue]`"`.
.Examples
`>>>` [blue]`"I'm a string"` +
[green]`I'm a string` +
[green]`I'm a string`
`>>>` [blue]`"123abc?!"` +
[green]`123abc?!` +
[green]`123abc?!`
`>>>` [blue]`"123\nabc"` +
[green]`123` +
[green]`abc` +
[green]`abc`
`>>>` [blue]`"123\tabc"` +
[green]`123{nbsp}{nbsp}{nbsp}{nbsp}abc`
Some arithmetic operators can also be used with strings.
Some arithmetic operators also apply to strings.
.String operators
[cols="^1,^2,6,4"]
@@ -300,29 +321,34 @@ Some arithmetic operators can also be used with strings.
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` -> _"oneone"_
|===
The items of strings can be accessed using the square `[]` operator.
The charanters in a string can be accessed using the square `[]` operator.
.Item access syntax
====
_item_ = _string-expr_ "**[**" _integer-expr_ "**]**"
*_item_* = _string-expr_ "**[**" _integer-expr_ "**]**"
====
.Sub string syntax
.Sub-string syntax
====
_sub-string_ = _string-expr_ "**[**" _integer-expr_ "**:**" _integer-expr_ "**]**"
*_sub-string_* = _string-expr_ "**[**" _integer-expr_ "**:**" _integer-expr_ "**]**"
====
.String examples
`>>>` [blue]`s="abcd"` [gray]_// assign the string to variable s_ +
[green]`"abcd"` +
[green]`"abcd"`
`>>>` [blue]`s[1]` [gray]_// char at position 1 (starting from 0)_ +
[green]`"b"` +
`>>>` [blue]`s.[-1]` [gray]_// char at position -1, the rightmost one_ +
[green]`"d"` +
`>>>` [blue]`\#s` [gray]_// number of chars_ +
[gren]`4` +
[green]`"b"`
`>>>` [blue]`s[-1]` [gray]_// char at position -1, the rightmost one_ +
[green]`"d"`
`>>>` [blue]`#s` [gray]_// number of chars_ +
[gren]`4`
`>>>` [blue]`#"abc"` [gray]_// number of chars_ +
[green]`3` +
[green]`3`
`>>>` [blue]`s[1:3]` [gray]_// chars from position 1 to position 3 excluded_ +
[grean]`"bc"`
@@ -343,9 +369,9 @@ Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relationa
| [blue]`\<=` | _Less or Equal_ | True if the left value is less than or equal to the right one | [blue]`5 \<= 2` -> _false_ +
[blue]`"b" \<= "b"` -> _true_
| [blue]`>` | _Greater_ | True if the left value is greater than the right one | [blue]`5 > 2` -> _true_ +
[blue]`"a" < "b"` -> _false_
[blue]`"a" > "b"` -> _false_
| [blue]`>=` | _Greater or Equal_ | True if the left value is greater than or equal to the right one | [blue]`5 >= 2` -> _true_ +
[blue]`"b" \<= "b"` -> _true_
[blue]`"b" >= "b"` -> _true_
|===
^(*)^ See also the [blue]`in` operator in the _list_ and _dictionary_ sections.
@@ -362,20 +388,22 @@ Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relationa
| [blue]`AND` / [blue]`&&` | _And_ | True if both left and right values are true | [blue]`false && true` -> _false_ +
[blue]`"a" < "b" AND NOT (2 < 1)` -> _true_
| [blue]`OR` / [blue]`\|\|` | _Or_ | True if at least one of the left and right values integers true| [blue]`false or true` -> _true_ +
| [blue]`OR` / [blue]`\|\|` | _Or_ | True if at least one of the left and right values integers is true| [blue]`false or true` -> _true_ +
[blue]`"a" == "b" OR (2 == 1)` -> _false_
|===
[CAUTION]
====
Currently, boolean operations are evaluated using _short cut evaluation_. This means that, if the left expression of the [blue]`and` and [blue]`or` operators is sufficient to establish the result of the whole operation, the right expression would not evaluated at all.
Currently, boolean operations are evaluated using _short cut evaluation_. This means that, if the left expression of the [blue]`and` and [blue]`or` operators is sufficient to establish the result of the whole operation, the right expression would not be evaluated at all.
.Example
[source,go]
----
2 > (a=1) or (a=8) > 0; a // <1>
----
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and as a conseguence its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
TIP: `dev-expr` provides the _ctrl()_ function that allows to change this behaviour.
====
=== Lists
@@ -385,18 +413,22 @@ _Expr_ supports list of mixed-type values, also specified by normal expressions.
====
*_list_* = _empty-list_ | _non-empty-list_ +
_empty-list_ = "**[]**" +
_non-empty-list_ = "**[**" _any-value_ {"**,**" _any-value} "**]**" +
_non-empty-list_ = "**[**" _any-value_ {"**,**" _any-value_} "**]**" +
====
.Examples
`>>>` [blue]`[1,2,3]` [gray]_// List of integers_ +
[green]`[1, 2, 3]` +
[green]`[1, 2, 3]`
`>>>` [blue]`["one", "two", "three"]` [gray]_// List of strings_ +
[green]`["one", "two", "three"]` +
[green]`["one", "two", "three"]`
`>>>` [blue]`["one", 2, false, 4.1]` [gray]_// List of mixed-types_ +
[green]`["one", 2, false, 4.1]` +
[green]`["one", 2, false, 4.1]`
`>>>` [blue]`["one"+1, 2.0*(9-2)]` [gray]_// List of expressions_ +
[green]`["one1", 14]` +
[green]`["one1", 14]`
`>>>` [blue]`[ [1,"one"], [2,"two"]]` [gray]_// List of lists_ +
[green]`[[1, "one"], [2, "two"]]`
@@ -409,40 +441,81 @@ _non-empty-list_ = "**[**" _any-value_ {"**,**" _any-value} "**]**" +
| [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` -> _[1,3]_
| [blue]`>>` | _Front insertion_ | Insert an item in front | [blue]`0 >> [1,2]` -> _[0,1,2]_
| [blue]`<<` | _Back insertion_ | Insert an item at end | [blue]`[1,2] << 3` -> _[1,2,3]_
| [blue]`.` | _List item_ | Item at given position | [blue]`[1,2.3].1` -> _2_
| [blue]`[]` | _Item at index_ | Item at given position | [blue]`[1,2,3][1]` -> _2_
| [blue]`in` | _Item in list_ | True if item is in list | [blue]`2 in [1,2,3]` -> _true_ +
[blue]`6 in [1,2,3]` -> _false_
| [blue]`#` | _Size_ | Number of items in a list | [blue]`#[1,2,3]` -> _3_
|===
The items of array can be accessed using the dot `.` operator.
Array's items can be accessed using the index `[]` operator.
.Item access syntax
====
_item_ = _list-expr_ "**.**" _integer-expr_
*_item_* = _list-expr_ "**[**" _integer-expr_ "**]**"
====
.Items of list
`>>>` [blue]`[1,2,3].1` +
[green]`2` +
`>>>` [blue]`list=[1,2,3]; list.1` +
[green]`2` +
`>>>` [blue]`["one","two","three"].1` +
[green]`two` +
`>>>` [blue]`list=["one","two","three"]; list.(2-1)` +
[green]`two` +
`>>>` [blue]`list.(-1)` +
[green]`three` +
`>>>` [blue]`list.(10)` +
[red]`Eval Error: [1:9] index 10 out of bounds` +
`>>>` [blue]`#list` +
[green]`3` +
`>>>` [blue]`index=2; ["a", "b", "c", "d"].index` +
.Sub-array (or slice of array) syntax
====
*_slice_* = _string-expr_ "**[**" _integer-expr_ "**:**" _integer-expr_ "**]**"
====
.Examples: Getting items from lists
`>>>` [blue]`[1,2,3][1]` +
[green]`2`
`>>>` [blue]`index=2; ["a", "b", "c", "d"][index]` +
[green]`c`
`>>>` [blue]`["a", "b", "c", "d"][2:]` +
[green]`["c", "d"]`
`>>>` [blue]`list=[1,2,3]; list[1]` +
[green]`2`
`>>>` [blue]`["one","two","three"][1]` +
[green]`two`
`>>>` [blue]`list=["one","two","three"]; list[2-1]` +
[green]`two`
`>>>` [blue]`list[1]="six"; list` +
[green]`["one", "six", "three"]`
`>>>` [blue]`list[-1]` +
[green]`three`
`>>>` [blue]`list[10]` +
[red]`Eval Error: [1:9] index 10 out of bounds`
.Example: Number of elements in a list
`>>>` [blue]`#list` +
[green]`3`
.Examples: Element insertion
`>>>` [blue]`"first" >> list` +
[green]`["first", "one", "six", "three"]`
`>>>` [blue]`list << "last"` +
[green]`["first", "one", "six", "three", "last"]`
.Examples: Element in list
`>>>` [blue]`"six" in list` +
[green]`true`
`>>>` [blue]`"ten" in list` +
[green]`false`
.Examples: Concatenation and filtering
`>>>` [blue]`[1,2,3] + ["one", "two", "three"]` +
[green]`[1, 2, 3, "one", "two", "three"]`
`>>>` [blue]`[1,2,3,4] - [2,4]` +
[green]`[1, 3]`
=== Dictionaries
The _dictionary_, or _dict_, data-type is set of pairs _key/value_. It is also known as _map_ or _associative array_.
The _dictionary_, or _dict_, data-type represents sets of pairs _key/value_. It is also known as _map_ or _associative array_.
Dictionary literals are sequences of pairs separated by comma [blue]`,` enclosed between brace brackets.
@@ -450,17 +523,14 @@ Dictionary literals are sequences of pairs separated by comma [blue]`,` enclosed
====
*_dict_* = _empty-dict_ | _non-empty-dict_ +
_empty-dict_ = "**{}**" +
_non-empty-dict_ = "**{**" _key-scalar_ "**:**" _any-value_ {"**,**" _key-scalar_ "**:**" _any-value} "**}**" +
_non-empty-dict_ = "**{**" _key-scalar_ "**:**" _any-value_ {"**,**" _key-scalar_ "**:**" _any-value_} "**}**" +
====
.Examples
`>>>` [blue]`{1:"one", 2:"two"}` +
[green]`{1: "one", 2: "two"}` +
`>>>` [blue]`{"one":1, "two": 2}` +
[green]`{"one": 1, "two": 2}` +
`>>>` [blue]`{"sum":1+2+3, "prod":1*2*3}` +
[green]`{"sum": 6, "prod": 6}`
.Item access syntax
====
*_item_* = _dict-expr_ "**[**" _key-expr_ "**]**"
====
.Dict operators
[cols="^2,^2,4,5"]
@@ -468,11 +538,31 @@ _non-empty-dict_ = "**{**" _key-scalar_ "**:**" _any-value_ {"**,**" _key-scalar
| Symbol | Operation | Description | Examples
| [blue]`+` | _Join_ | Joins two dicts | [blue]`{1:"one"}+{6:"six"}` -> _{1: "one", 6: "six"}_
| [blue]`.` | _Dict item value_ | Item value of given key | [blue]`{"one":1, "two":2}."two"` -> _2_
| [blue]`[]` | _Dict item value_ | Item value of given key | [blue]`{"one":1, "two":2}["two"]` -> _2_
| [blue]`in` | _Key in dict_ | True if key is in dict | [blue]`"one" in {"one":1, "two":2}` -> _true_ +
[blue]`"six" in {"one":1, "two":2}` -> _false_
| [blue]`#` | _Size_ | Number of items in a dict | [blue]`#{1:"a",2:"b",3:"c"}` -> _3_
|===
.Examples
`>>>` [blue]`{1:"one", 2:"two"}` +
[green]`{1: "one", 2: "two"}`
`>>>` [blue]`{"one":1, "two": 2}` +
[green]`{"one": 1, "two": 2}`
`>>>` [blue]`{"sum":1+2+3, "prod":1*2*3}` +
[green]`{"sum": 6, "prod": 6}`
`>>>` [blue]`{"one":1, "two":2}["two"]` +
[green]`2`
`>>>` [blue]`d={"one":1, "two":2}; d["six"]=6; d` +
[green]`{"two": 2, "one": 1, "six": 6}`
`>>>` [blue]`#d` +
[green]`3`
== Variables
_Expr_, like most programming languages, supports variables. A variable is an identifier with an assigned value. Variables are stored in _contexts_.
@@ -488,17 +578,23 @@ NOTE: The assign operator [blue]`=` returns the value assigned to the variable.
.Examples
`>>>` [blue]`a=1` +
[green]`1` +
[green]`1`
`>>>` [blue]`a_b=1+2` +
[green]`1+2` +
[green]`3`
`>>>` [blue]`a_b` +
[green]`3` +
`>>>` [blue]`x = 5.2 * (9-3)` [gray]_// The assigned value has the approximation error typical of the float data-type_ +
[green]`31.200000000000003` +
[green]`3`
`>>>` [blue]`x = 5.2 * (9-3)` [gray]_// The assigned value here has the typical approximation error of the float data-type_ +
[green]`31.200000000000003`
`>>>` [blue]`x = 1; y = 2*x` +
[green]`2` +
`>>>` [blue]`_a=2` +
[red]`Parse Error: [1:2] unexpected token "_"` +
[green]`2`
`>>>` [blue]`\_a=2` +
[red]`Parse Error: [1:2] unexpected token "_"`
`>>>` [blue]`1=2` +
[red]`Parse Error: assign operator ("=") must be preceded by a variable`
@@ -508,7 +604,12 @@ NOTE: The assign operator [blue]`=` returns the value assigned to the variable.
=== [blue]`;` operator
The semicolon operator [blue]`;` is an infixed pseudo-operator. It evaluates the left expression first and then the right expression. The value of the latter is the final result.
An expression that contains [blue]`;` is called a _multi-expression_ and each component expressione is called a _sub-expression_.
.Multi-expression syntax
====
*_multi-expression_* = _expression_ {"**;**" _expression_ }
====
An expression that contains [blue]`;` is called a _multi-expression_ and each component expression is called a _sub-expression_.
IMPORTANT: Technically [blue]`;` is not treated as a real operator. It acts as a separator in lists of expressions.
@@ -518,7 +619,7 @@ TIP: [blue]`;` can be used to set some variables before the final calculation.
`>>>` [blue]`a=1; b=2; c=3; a+b+c` +
[green]`6`
The value of each sub-expression is stored in the automatica variable _last_.
The value of each sub-expression is stored in the automatic variable _last_.
.Example
`>>>` [blue]`2+3; b=last+10; last` +
@@ -529,9 +630,10 @@ The value of each sub-expression is stored in the automatica variable _last_.
[blue]`but` is an infixed operator. Its operands can be expressions of any type. It evaluates the left expression first, then the right expression. The value of the right expression is the final result.
.Examples
[blue]`5 but 2` +
[green]`2` +
[blue]`x=2*3 but x-1` +
`>>>` [blue]`5 but 2` +
[green]`2`
`>>>` [blue]`x=2*3 but x-1` +
[green]`5`.
[blue]`but` behavior is very similar to [blue]`;`. The only difference is that [blue]`;` is not a true operator and can't be used inside parenthesis [blue]`(` and [blue]`)`.
@@ -539,17 +641,21 @@ The value of each sub-expression is stored in the automatica variable _last_.
=== Assignment operator [blue]`=`
The assignment operator [blue]`=` is used to define variables or to change their value in the evaluation context (see _ExprContext_).
The value on the left side of [blue]`=` must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.
The value on the left side of [blue]`=` must be a variable identifier or an expression that evalutes to a variable. The value on the right side can be any expression and it becomes the result of the assignment operation.
.Example
`>>>` [blue]`a=15+1`
.Examples
`>>>` [blue]`a=15+1` +
[green]`16`
`>>>` [blue]`L=[1,2,3]; L[1]=5; L` +
[green]`[1, 5, 3]`
=== Selector operator [blue]`? : ::`
The _selector operator_ is very similar to the _switch/case/default_ statement available in many programming languages.
.Selector literal Syntax
====
_selector-operator_ = _select-expression_ "*?*" _selector-case_ { "*:*" _selector-case_ } ["*::*" _default-multi-expression_] +
_selector-case_ = [_match-list_] _case-value_ +
_match-list_ = "*[*" _item_ {"*,*" _items_} "*]*" +
@@ -557,6 +663,7 @@ _item_ = _expression_ +
_case-multi-expression_ = "*{*" _multi-expression_ "*}*" +
_multi-expression_ = _expression_ { "*;*" _expression_ } +
_default-multi-expression_ = _multi-expression_
====
In other words, the selector operator evaluates the _select-expression_ on the left-hand side of the [blue]`?` symbol; it then compares the result obtained with the values listed in the __match-list__'s, from left to right. If the comparision finds a match with a value in a _match-list_, the associated _case-multi-expression_ is evaluted, and its result will be the final result of the selection operation.
@@ -566,40 +673,62 @@ The [blue]`:` symbol (colon) is the separator of the selector-cases. Note that i
.Examples
`>>>` [blue]`1 ? {"a"} : {"b"}` +
[green]`b` +
`>>>` [blue]`10 ? {"a"} : {"b"} :: {"c"}` +
[green]`c' +
[green]`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}` +
[green]`b` +
`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}` +
[red]`Parse Error: [1:34] case list in default clause` +
[green]`>>>` [blue]`10 ? {"a"} :[10] {x="b" but x} :: {"c"}` +
[green]`b` +
`>>>` [blue]`10 ? {"a"} :[10] {x="b"; x} :: {"c"}` +
[green]`b` +
[green]`b`
`>>>` [blue]`10 ? {"a"} : {"b"} {2c} {"c"}` +
[green]`c`
`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} {2c} {"c"}` +
[green]`b`
`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} {2c} [10] {"c"}` +
[red]`Parse Error: [1:34] case list in default clause`
`>>>` [blue]`10 ? {"a"} :[10] {x="b" but x} {2c} {"c"}` +
[green]`b`
`>>>` [blue]`10 ? {"a"} :[10] {x="b"; x} {2c} {"c"}` +
[green]`b`
`>>>` [blue]`10 ? {"a"} : {"b"}` +
[red]`Eval Error: [1:3] no case catches the value (10) of the selection expression`
=== Variable default value [blue]`??` and [blue]`?=`
The left operand of these two operators must be a variable. The right operator can be any expression. They return the value of the variable if this is define; otherwise they return the value of the right expression.
=== Variable default value [blue]`??`, [blue]`?=`, and [blue]`?!`
The left operand of first two operators, [blue]`??` and [blue]`?=`, must be a variable. The right operator can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression.
IMPORTANT: If the left variable is defined, the right expression is not evuated at all.
IMPORTANT: If the left variable is defined, the right expression is not evaluated at all.
The [blue]`??` do not change the status of the left variable.
The [blue]`??` operator do not change the status of the left variable.
The [blue]`?=` assigns the calculated value of the right expression to the left variable.
The third one, [blue]`?!`, is the alternate operator. If the variable on the left size is not defined, it returns [blue]_nil_. Otherwise it returns the result of the expressione on the right side.
IMPORTANT: If the left variable is NOT defined, the right expression is not evaluated at all.
.Examples
`>>>` [blue]`var ?? (1+2)`'
[green]`3` +
`>>>` [blue]`var ?? (1+2)` +
[green]`3`
`>>>` [blue]`var` +
[red]`Eval Error: undefined variable or function "var"` +
[red]`Eval Error: undefined variable or function "var"`
`>>>` [blue]`var ?= (1+2)` +
[green]`3` +
[green]`3`
`>>>` [blue]`var` +
[green]`3`
`>>>` [blue]`x ?! 5` +
[green]`nil`
`>>>` [blue]`x=1; x ?! 5` +
[green]`5`
`>>>` [blue]`y ?! (c=5); c` +
[red]`Eval Error: undefined variable or function "c"`
NOTE: These operators have a high priority, in particular higher than the operator [blue]`=`.
== Priorities of operators
@@ -610,57 +739,213 @@ The table below shows all supported operators by decreasing priorities.
|===
| Priority | Operators | Position | Operation | Operands and results
.2+|*ITEM*| [blue]`.` | _Infix_ | _List item_| _list_ `"."` _integer_ -> _any_
| [blue]`.` | _Infix_ | _Dict item_ | _dict_ `"."` _any_ -> _any_
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `"++"` -> _integer_
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `"++"` -> _any_
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `"!"` -> _integer_
.3+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| (`"+"`\|`"-"`) _number_ -> _number_
| [blue]`#` | _Prefix_ | _Lenght-of_ | `"#"` _collection_ -> _integer_
| [blue]`#` | _Prefix_ | _Size-of_ | `"#"` _iterator_ -> _integer_
.5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ `"*"` _number_ -> _number_
| [blue]`*` | _Infix_ | _String-repeat_ | _string_ `"*"` _integer_ -> _string_
| [blue]`/` | _Infix_ | _Division_ | _number_ `"/"` _number_ -> _number_
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `"./"` _number_ -> _float_
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `"%"` _integer_ -> _integer_
.6+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `"+"` _number_ -> _number_
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `"+"` (_string_\|_number_) -> _string_
| [blue]`+` | _Infix_ | _List-join_ | _list_ `"+"` _list_ -> _list_
| [blue]`+` | _Infix_ | _Dict-join_ | _dict_ `"+"` _dict_ -> _dict_
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `"-"` _number_ -> _number_
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `"-"` _list_ -> _list_
.8+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ `"<"` _comparable_ -> _boolean_
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `"\<="` _comparable_ -> _boolean_
| [blue]`>` | _Infix_ | _greater_ | _comparable_ `">"` _comparable_ -> _boolean_
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ `">="` _comparable_ -> _boolean_
| [blue]`==` | _Infix_ | _equal_ | _comparable_ `"=="` _comparable_ -> _boolean_
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ `"!="` _comparable_ -> _boolean_
| [blue]`in` | _Infix_ | _member-of-list_ | _any_ `"in"` _list_ -> _boolean_
| [blue]`in` | _Infix_ | _key-of-dict_ | _any_ `"in"` _dict_ -> _boolean_
.1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | `"not"` _boolean_ -> _boolean_
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ `"and"` _boolean_ -> _boolean_
| [blue]`&&` | _Infix_ | _and_ | _boolean_ `"&&"` _boolean_ -> _boolean_
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ `"or"` _boolean_ -> _boolean_
| [blue]`\|\|` | _Infix_ | _or_ | _boolean_ `"\|\|"` _boolean_ -> _boolean_
.3+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
| [blue]`>>` | _Infix_ | _front-insert_ | _any_ ">>" _list_ -> _list_
| [blue]`<<` | _Infix_ | _back-insert_ | _list_ "<<" _any_ -> _list_
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
.2+|*ITEM*| [blue]`[`...`]` | _Postfix_ | _List item_| _list_ `[` _integer_ `]` -> _any_
| [blue]`[`...`]` | _Postfix_ | _Dict item_ | _dict_ `[` _any_ `]` -> _any_
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `++` -> _integer_
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `++` -> _any_
.3+|*DEFAULT*| [blue]`??` | _Infix_ | _Default value_| _variable_ `??` _any-expr_ -> _any_
| [blue]`?=` | _Infix_ | _Default/assign value_| _variable_ `?=` _any-expr_ -> _any_
| [blue]`?!` | _Infix_ | _Alternate value_| _variable_ `?!` _any-expr_ -> _any_
//.1+| *ITER*^1^| [blue]`()` | _Prefix_ | _Iterator value_ | `()` _iterator_ -> _any_
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `!` -> _integer_
.3+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| (`+`\|`-`) _number_ -> _number_
| [blue]`#` | _Prefix_ | _Lenght-of_ | `#` _collection_ -> _integer_
| [blue]`#` | _Prefix_ | _Size-of_ | `#` _iterator_ -> _integer_
.2+|*SELECT*| [blue]`? : ::` | _Multi-Infix_ | _Case-Selector_ | _any-expr_ `?` _case-list_ _case-expr_ `:` _case-list_ _case-expr_ ... `::` _default-expr_ -> _any_
| [blue]`? : ::` | _Multi-Infix_ | _Index-Selector_ | _int-expr_ `?` _case-expr_ `:` _case-expr_ ... `::` _default-expr_ -> _any_
.1+|*FRACT*| [blue]`:` | _Infix_ | _Fraction_ | _integer_ `:` _integer_ -> _fraction_
.5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ `*` _number_ -> _number_
| [blue]`*` | _Infix_ | _String-repeat_ | _string_ `*` _integer_ -> _string_
| [blue]`/` | _Infix_ | _Division_ | _number_ `/` _number_ -> _number_
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `./` _number_ -> _float_
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `%` _integer_ -> _integer_
.6+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `+` _number_ -> _number_
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `+` (_string_\|_number_) -> _string_
| [blue]`+` | _Infix_ | _List-join_ | _list_ `+` _list_ -> _list_
| [blue]`+` | _Infix_ | _Dict-join_ | _dict_ `+` _dict_ -> _dict_
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `-` _number_ -> _number_
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `-` _list_ -> _list_
.3+|*BINARY*| [blue]`&` | _Infix_ | _Binary And_ | _number_ `&` _number_ -> _number_
| [blue]`\|` | _Infix_ | _Binary Or_ | _number_ `\|` _number_ -> _number_
| [blue]`~` | _Prefix_ | _Binary Not_ | `~` _number_ -> _number_
.8+|*RELATION*| [blue]`<` | _Infix_ | _Less_ | _comparable_ `<` _comparable_ -> _boolean_
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `\<=` _comparable_ -> _boolean_
| [blue]`>` | _Infix_ | _Greater_ | _comparable_ `>` _comparable_ -> _boolean_
| [blue]`>=` | _Infix_ | _Greater-equal_ | _comparable_ `>=` _comparable_ -> _boolean_
| [blue]`==` | _Infix_ | _Equal_ | _comparable_ `==` _comparable_ -> _boolean_
| [blue]`!=` | _Infix_ | _Not-equal_ | _comparable_ `!=` _comparable_ -> _boolean_
| [blue]`in` | _Infix_ | _Member-of-list_ | _any_ `in` _list_ -> _boolean_
| [blue]`in` | _Infix_ | _Key-of-dict_ | _any_ `in` _dict_ -> _boolean_
.1+|*NOT*| [blue]`not` | _Prefix_ | _Not_ | `not` _boolean_ -> _boolean_
.2+|*AND*| [blue]`and` | _Infix_ | _And_ | _boolean_ `and` _boolean_ -> _boolean_
| [blue]`&&` | _Infix_ | _And_ | _boolean_ `&&` _boolean_ -> _boolean_
.2+|*OR*| [blue]`or` | _Infix_ | _Or_ | _boolean_ `or` _boolean_ -> _boolean_
| [blue]`\|\|` | _Infix_ | _Or_ | _boolean_ `\|\|` _boolean_ -> _boolean_
.3+|*ASSIGN*| [blue]`=` | _Infix_ | _Assignment_ | _identifier_ `=` _any_ -> _any_
| [blue]`>>` | _Infix_ | _Front-insert_ | _any_ `>>` _list_ -> _list_
| [blue]`<<` | _Infix_ | _Back-insert_ | _list_ `<<` _any_ -> _list_
.1+|*BUT*| [blue]`but` | _Infix_ | _But_ | _any_ `but` _any_ -> _any_
.1+|*RANGE*| [blue]`:` | _Infix_ | _Index-range_ | _integer_ `:` _integer_ -> _integer-pair_
|===
//^1^ Experimental
== Functions
Functions in _Expr_ are very similar to functions available in many programming languages. Actually, _Expr_ supports two types of function, _expr-functions_ and _go-functions_.
Functions in _Expr_ are very similar to functions available in many programming languages. Currently, _Expr_ supports two types of function, _expr-functions_ and _go-functions_.
* _expr-functions_ are defined using _Expr_'s syntax. They can be passed as arguments to other functions and can be returned from functions. Moreover, they bind themselves to the defining context, thus becoming closures.
* _go-functions_ are regular Golang functions callable from _Expr_ expressions. They are defined in Golang source files called _modules_ and compiled within the _Expr_ package. To make Golang functions available in _Expr_ contextes, it is required to _import_ the module in which they are defined.
* _go-functions_ are regular Golang functions callable from _Expr_ expressions. They are defined in Golang source files called _modules_ and compiled within the _Expr_ package. To make Golang functions available in _Expr_ contextes, it is required to activate the builtin module or to load the plugin module in which they are defined.
In _Expr_ functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator [blue]`@` it is possibile to export local definition to the calling context.
=== _Expr_ function definition
A function is identified and referenced by its name. It can have zero or more parameter. _Expr_ functions also support optional parameters and passing paramters by name.
.Expr's function definition syntax
====
*_function-definition_* = _identifier_ "**=**" "**func(**" [_formal-param-list_] "**)**" "**{**" _multi-expression_ "**}**" +
_formal-param_list_ = _required-param-list_ [ "**,**" _optional-param-list_ ] +
_required-param-list_ = _identifier_ { "**,**" _identifier_ } +
_optional-param-list_ = _optional-parm_ { "**,**" _optional-param_ } +
_optional-param_ = _param-name_ "**=**" _any-expr_ +
_param-name_ = _identifier_
====
.Examples
`>>>` [gray]_// A simple function: it takes two parameters and returns their "sum"_**^(*)^** +
`>>>` [blue]`sum = func(a, b){ a + b }` +
[green]`sum(a, b):any{}`
^(\*)^ Since the plus, *+*, operator is defined for multiple data-types, the _sum()_ function can be used for any pair of that types.
`>>>` [gray]_// A more complex example: recursive calculation of the n-th value of Fibonacci's sequence_ +
`>>>` [blue]`fib = func(n){ n ? [0] {0}: [1] {1} :: {fib(n-1)+fib(n-2)} }` +
[green]`fib(n):any{}`
`>>>` [gray]_// Same function fib() but entered by splitting it over mulple text lines_ +
`>>>` [blue]`fib = func(n){ \` +
`\...` [blue]`{nbsp}{nbsp}n ? \` +
`\...` [blue]`{nbsp}{nbsp}{nbsp}{nbsp}[0] {0} : \` +
`\...` [blue]`{nbsp}{nbsp}{nbsp}{nbsp}[1] {1} :: \` +
`\...` [blue]`{nbsp}{nbsp}{nbsp}{nbsp}{ \` +
`\...` [blue]`{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}{nbsp}fib(n-1) + fib(n-2) \` +
`\...` [blue]`{nbsp}{nbsp}{nbsp}{nbsp}} \` +
`\...` [blue]`}` +
[green]`fib(n):any{}`
`>>>` [gray]_// Required and optional parameters_ +
`>>>` [blue]`measure = func(value, unit="meter"){ value + " " + unit + (value > 1) ? [true] {"s"} :: {""}}` +
[green]`measure(value, unit="meter"):any{}`
=== _Golang_ function definition
Description of how to define Golan functions and how to bind them to _Expr_ are topics treated in another document that I'll write, one day, maybe.
=== Function calls
#TODO: function calls operations#
To call a function, either Expr or Golang type, it is necessary to specify its name and, at least, its required parameters.
=== Function definitions
#TODO: function definitions operations#
.Function invocation syntax
====
*_function-call_* = _identifier_ "**(**" _actual-param-list_ "**)**" +
_actual-param-list_ = [_positional-params_] [_named-parameters_] +
_positional-params_ = _any-value_ { "*,*" _any-value_ } +
_named-params_ = _param-name_ "**=**" _any-value_ { "*,*" _param-name_ "**=**" _any-value_ } +
_param-name_ = _identifier_
====
.Examples of calling the `sum()` functions defined above
`>>>` [gray]_// sum of two integers_ +
`>>>` [blue]`sum(-6, 2)` +
[green]`-4` +
`>>>` [gray]_// same as above but passing the parameters by name_ +
`>>>` [blue]`sum(a=-6, b=2)` +
[green]`-4` +
`>>>` [gray]_// again, but swapping parameter positions (see the diff() examples below)_ +
`>>>` [blue]`sum(b=2, a=-6)` +
[green]`-4` +
`>>>` [gray]_// sum of a fraction and an integer_ +
`>>>` [blue]`sum(3|2, 2)` +
[green]`7|2` +
`>>>` [gray]_// sum of two strings_ +
`>>>` [blue]`sum("bye", "-bye")` +
[green]`"bye-bye"` +
`>>>` [gray]_// sum of two lists_ +
`>>>` [blue]`sum(["one", 1], ["two", 2])` +
[green]`["one", 1, "two", 2]`
.Examples of calling a function with parameters passed by name
`>>>` [gray]_// diff(a,b) calculates a-b_ +
`>>>` [blue]`diff = func(a,b){a-b}` +
[green]`diff(a, b):any{}` +
`>>>` [gray]_// simple invocation_ +
`>>>` [blue]`diff(10,8)` +
[green]`2` +
`>>>` [gray]_// swapped parameters passed by name_ +
`>>>` [blue]`diff(b=8,a=10)` +
[green]`2`
.Examples of calling the `fib()` function defined above
`>>>` [gray]_// Fibonacci sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ..._ +
`>>>` [blue]`fib(6)` +
[green]`8` +
`>>>` [blue]`fib(9)` +
[green]`34`
.Examples of calling the `measure()` functions defined above
`>>>` [gray]_// simple call_ +
`>>>` [blue]`measure(10,"litre")` +
[green]`"10 litres"` +
`>>>` [gray]_// accept the default unit_ +
`>>>` [blue]`measure(8)` +
[green]`"8 meters"` +
`>>>` [gray]_// without the required parameter 'value'_ +
`>>>` [blue]`measure(unit="degrees"))` +
[red]`Eval Error: measure(): missing params -- value`
.Examples of context binding (closures)
`>>>` [blue]`factory = func(n=2){ func(x){x*n} }` +
[green]`factory(n=2):any{}` +
`>>>` [blue]`double = factory()` +
[green]`double(x):any{}` +
`>>>` [blue]`triple = factory(3)` +
[green]`triple(x):any{}` +
`>>>` [blue]`double(5)` +
[green]`10` +
`>>>` [blue]`triple(5)` +
[green]`15`
=== Function context
Functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the _clone_ modifier [blue]`@` it is possibile to export local definition to the calling context. The clone modifier must be used as prefix to variable names and it is part of the name. E.g. [blue]`@x` is not the same as [blue]`x`; they are two different and un related variables.
Clone variables are normal local variables. The only diffence will appear when the defining function terminate, just before the destruction of its local context. At that point, all local clone variables are cloned in the calling context with the same names but the [blue]`@` symbol.
.Example
`>>>` [blue]`f = func() { @x = 3; x = 5 }` [gray]_// f() declares two *different* local variables: ``@x`` and ``x``_ +
[green]`f():any{}` +
`>>>` [blue]`f()` [gray]_// The multi-expression (two) in f() is calculated and the last result is returned_ +
[green]`5` +
`>>>` [blue]`x` [gray]_// The `x` variable was not defined in the main context before the f() invocation. It appears in the main context by cloning the `@x` variable, local to f() after its termnation._ +
[green]`3`
NOTE: The clone modifier [blue]`@` does not make a variable a reference variable, as the ampersand character `&` does in languages such as C and C++.
[IMPORTANT]
====
The clone modifier can also be used to declare the formal parameters of functions, because they are local variables too.
.Example
`>>>` [blue]`g = func(@p) {2+@p}`
g(@p):any{}`
`>>>` [blue]`g(9)`
11`
`>>>` [blue]`p
9
====
== Iterators
#TODO: function calls operations#
== Builtins
#TODO: builtins#
@@ -671,4 +956,7 @@ In _Expr_ functions compute values in a local context (scope) that do not make e
[blue]_import([grey]#<source-file>#)_ loads the multi-expression contained in the specified source and returns its value.
== Plugins
#TODO: plugins#
+763 -252
View File
File diff suppressed because one or more lines are too long
+29
View 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
View 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
View 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
}
+15 -13
View File
@@ -50,9 +50,10 @@ func makeGeneratingFraction(s string) (f *FractionType, err error) {
} else if s[0] == '+' {
s = s[1:]
}
if strings.HasSuffix(s, "()") {
s = s[0 : len(s)-2]
}
// 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
@@ -123,36 +124,38 @@ func (f *FractionType) String() string {
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))
sb.WriteString(fmt.Sprintf("%d:%d", f.num, f.den))
} else {
var s, num string
var sign, num string
if f.num < 0 && opt&TTY == 0 {
num = strconv.FormatInt(-f.num, 10)
s = "-"
sign = "-"
} 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)))
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(s) > 0 {
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(s) > 0 {
sb.WriteString(s)
if len(sign) > 0 {
sb.WriteString(sign)
sb.WriteByte(' ')
}
sb.WriteString(strings.Repeat("-", size))
sb.WriteByte('\n')
if len(s) > 0 {
if len(sign) > 0 {
sb.WriteString(" ")
}
}
sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den)))
sDen := fmt.Sprintf("%[1]*s", size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den))
sb.WriteString(sDen)
}
return sb.String()
@@ -311,7 +314,6 @@ func divAnyFract(af1, af2 any) (quot any, err error) {
if f2.num == 0 {
err = errors.New("division by zero")
return
return
}
if f1.num == 0 || f2.den == 0 {
quot = 0
+175 -29
View File
@@ -6,11 +6,12 @@ package expr
import (
"fmt"
"strconv"
"strings"
)
// ---- Function template
type FuncTemplate func(ctx ExprContext, name string, args []any) (result any, err error)
type FuncTemplate func(ctx ExprContext, name string, args map[string]any) (result any, err error)
// ---- Common functor definition
type baseFunctor struct {
@@ -42,6 +43,10 @@ func (functor *baseFunctor) GetFunc() ExprFunc {
return functor.info
}
func (functor *baseFunctor) GetDefinitionContext() ExprContext {
return nil
}
// ---- Function Parameters
type paramFlags uint16
@@ -94,19 +99,20 @@ func (param *funcParamInfo) DefaultValue() any {
}
// --- Functions
// funcInfo implements ExprFunc
type funcInfo struct {
name string
minArgs int
maxArgs int
functor Functor
params []ExprFuncParam
formalParams []ExprFuncParam
returnType string
}
func newFuncInfo(name string, functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
var minArgs = 0
var maxArgs = 0
if params != nil {
for _, p := range params {
if maxArgs == -1 {
return nil, fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name())
@@ -124,20 +130,16 @@ func newFuncInfo(name string, functor Functor, returnType string, params []ExprF
maxArgs = -1
}
}
}
info = &funcInfo{
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, params: params,
name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor, returnType: returnType, formalParams: params,
}
functor.SetFunc(info)
return info, nil
}
func newUnnamedFuncInfo(functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
return newFuncInfo("unnamed", functor, returnType, params)
}
func (info *funcInfo) Params() []ExprFuncParam {
return info.params
return info.formalParams
}
func (info *funcInfo) ReturnType() string {
@@ -152,8 +154,8 @@ func (info *funcInfo) ToString(opt FmtOpt) string {
sb.WriteString(info.Name())
}
sb.WriteByte('(')
if info.params != nil {
for i, p := range info.params {
if info.formalParams != nil {
for i, p := range info.formalParams {
if i > 0 {
sb.WriteString(", ")
}
@@ -200,27 +202,129 @@ func (info *funcInfo) Functor() Functor {
return info.functor
}
// ----- Call a function ---
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 checkFunctionCall(ctx ExprContext, name string, varParams *[]any) (err error) {
if info, exists, owner := GetFuncInfo(ctx, name); exists {
passedCount := len(*varParams)
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
}
for i, p := range info.Params() {
if i >= passedCount {
if passedCount < len(info.formalParams) {
for _, p := range info.formalParams {
if _, exists := actualParams[p.Name()]; !exists {
if !p.IsDefault() {
break
}
*varParams = append(*varParams, p.DefaultValue())
if p.IsRepeat() {
varArgs := make([]any, 1)
varArgs[0] = p.DefaultValue()
actualParams[p.Name()] = varArgs
} else {
actualParams[p.Name()] = p.DefaultValue()
}
}
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(*varParams) {
err = ErrTooMuchParams(name, info.MaxArgs(), len(*varParams))
}
if err == nil && owner != ctx {
ctx.RegisterFuncInfo(info)
}
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)
@@ -228,13 +332,55 @@ func checkFunctionCall(ctx ExprContext, name string, varParams *[]any) (err erro
return
}
func CallFunction(parentCtx ExprContext, name string, params []any) (result any, err error) {
ctx := cloneContext(parentCtx)
ctx.SetParent(parentCtx)
if err = checkFunctionCall(ctx, name, &params); err == nil {
result, err = ctx.Call(name, params)
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
}
+14 -1
View File
@@ -55,7 +55,20 @@ func GetLocalFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool)
}
return
}
func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool, ownerCtx ExprContext) {
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
+5 -1
View File
@@ -1,3 +1,7 @@
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
View 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=
+14 -6
View File
@@ -25,13 +25,13 @@ func checkStringParamExpected(funcName string, paramValue any, paramPos int) (er
return
}
func addSourceEnvImportDirs(varName string, dirList []string) []string {
return addEnvImportDirs(ENV_EXPR_SOURCE_PATH, dirList)
}
// 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 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 {
@@ -75,7 +75,11 @@ func isFile(filePath string) bool {
}
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
@@ -90,6 +94,10 @@ func isPathRelative(filePath string) bool {
}
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
+18 -12
View File
@@ -5,38 +5,44 @@
package expr
import (
"errors"
// "errors"
"fmt"
)
// Operator names
const (
initName = "init"
cleanName = "clean"
resetName = "reset"
nextName = "next"
currentName = "current"
indexName = "index"
countName = "count"
InitName = "init"
CleanName = "clean"
ResetName = "reset"
NextName = "next"
CurrentName = "current"
IndexName = "index"
CountName = "count"
FilterName = "filter"
MapName = "map"
)
type Iterator interface {
Typer
Next() (item any, err error) // must return io.EOF after the last item
Current() (item any, err error)
Index() int
Count() int
}
type ExtIterator interface {
Iterator
Reset() error
Clean() error
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 {
return fmt.Errorf("no %s() function defined in the data-source", name)
}
func errInvalidDataSource() error {
return errors.New("invalid data-source")
}
// func errInvalidDataSource() error {
// return errors.New("invalid data-source")
// }
+32 -13
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// iter-list.go
// list-iterator.go
package expr
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}
if argc >= 1 {
if i, err := ToInt(args[0], "start index"); err == nil {
if i, err := ToGoInt(args[0], "start index"); err == nil {
if i < 0 {
i = listLen + i
}
it.start = i
}
if argc >= 2 {
if i, err := ToInt(args[1], "stop index"); err == nil {
if i, err := ToGoInt(args[1], "stop index"); err == nil {
if i < 0 {
i = listLen + i
}
it.stop = i
}
if argc >= 3 {
if i, err := ToInt(args[2], "step"); err == nil {
if i, err := ToGoInt(args[2], "step"); err == nil {
if i < 0 {
i = -i
}
@@ -85,18 +85,28 @@ func (it *ListIterator) String() string {
return fmt.Sprintf("$(#%d)", l)
}
func (it *ListIterator) TypeName() string {
return "ListIterator"
}
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
}
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 {
case resetName:
v, err = it.Reset()
case indexName:
case NextName:
v, err = it.Next()
case ResetName:
err = it.Reset()
case CleanName:
err = it.Clean()
case IndexName:
v = int64(it.Index())
case countName:
case CurrentName:
v, err = it.Current()
case CountName:
v = it.count
default:
err = errNoOperation(name)
@@ -135,7 +145,16 @@ func (it *ListIterator) Index() int {
return it.index
}
func (it *ListIterator) Reset() (bool, error) {
it.index = it.start
return true, nil
func (it *ListIterator) Count() int {
return it.count
}
func (it *ListIterator) Reset() (error) {
it.index = it.start - it.step
it.count = 0
return nil
}
func (it *ListIterator) Clean() (error) {
return nil
}
+14 -13
View File
@@ -26,9 +26,10 @@ func newList(listAny []any) (list *ListType) {
func NewList(listAny []any) (list *ListType) {
if listAny != nil {
ls := make(ListType, len(listAny))
for i, item := range listAny {
ls[i] = item
}
// for i, item := range listAny {
// ls[i] = item
// }
copy(ls, listAny)
list = &ls
}
return
@@ -105,16 +106,16 @@ 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 (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) {
+2 -2
View File
@@ -18,8 +18,8 @@ func newDictTerm(args map[any]*term) *term {
}
// -------- dict func
func evalDict(ctx ExprContext, self *term) (v any, err error) {
dict, _ := self.value().(map[any]*term)
func evalDict(ctx ExprContext, opTerm *term) (v any, err error) {
dict, _ := opTerm.value().(map[any]*term)
items := make(DictType, len(dict))
for key, tree := range dict {
var param any
+3 -3
View File
@@ -20,11 +20,11 @@ func newExprTerm(root *term) *term {
}
// -------- eval expr
func evalExpr(ctx ExprContext, self *term) (v any, err error) {
if expr, ok := self.value().(*term); ok {
func evalExpr(ctx ExprContext, opTerm *term) (v any, err error) {
if expr, ok := opTerm.value().(*term); ok {
v, err = expr.compute(ctx)
} else {
err = fmt.Errorf("expression expected, got %T", self.value())
err = fmt.Errorf("expression expected, got %T", opTerm.value())
}
return
}
+23 -17
View File
@@ -21,20 +21,26 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
}
// -------- eval func call
func evalFuncCall(ctx ExprContext, self *term) (v any, err error) {
name, _ := self.tk.Value.(string)
params := make([]any, len(self.children), len(self.children)+5)
for i, tree := range self.children {
var param any
if param, err = tree.compute(ctx); err != nil {
break
}
params[i] = param
}
// func _evalFuncCall(ctx ExprContext, opTerm *term) (v any, err error) {
// name, _ := opTerm.tk.Value.(string)
// params := make([]any, len(opTerm.children), len(opTerm.children)+5)
// for i, tree := range opTerm.children {
// var param any
// if param, err = tree.compute(ctx); err != nil {
// break
// }
// params[i] = param
// }
if err == nil {
v, err = CallFunction(ctx, name, params)
}
// if err == nil {
// v, err = CallFunction(ctx, name, params)
// }
// return
// }
func evalFuncCall(ctx ExprContext, opTerm *term) (v any, err error) {
name, _ := opTerm.tk.Value.(string)
v, err = CallFunctionByTerm(ctx, name, opTerm)
return
}
@@ -51,11 +57,11 @@ func newFuncDefTerm(tk *Token, args []*term) *term {
}
// -------- eval func def
func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
bodySpec := self.value()
func evalFuncDef(ctx ExprContext, opTerm *term) (v any, err error) {
bodySpec := opTerm.value()
if expr, ok := bodySpec.(*ast); ok {
paramList := make([]ExprFuncParam, 0, len(self.children))
for _, param := range self.children {
paramList := make([]ExprFuncParam, 0, len(opTerm.children))
for _, param := range opTerm.children {
var defValue any
flags := paramFlags(0)
if len(param.children) > 0 {
+30 -47
View File
@@ -5,29 +5,12 @@
package expr
import (
"fmt"
"slices"
"strings"
)
// -------- 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 {
tk.Sym = SymIterator
return &term{
@@ -42,9 +25,9 @@ func newIteratorTerm(tk *Token, args []*term) *term {
// -------- eval iterator
func evalTermArray(ctx ExprContext, a []*term) (values []any, err error) {
values = make([]any, len(a))
for i, t := range a {
func evalTermArray(ctx ExprContext, terms []*term) (values []any, err error) {
values = make([]any, len(terms))
for i, t := range terms {
var value any
if value, err = t.compute(ctx); err == nil {
values[i] = value
@@ -55,26 +38,24 @@ func evalTermArray(ctx ExprContext, a []*term) (values []any, err error) {
return
}
func evalFirstChild(ctx ExprContext, self *term) (value any, err error) {
if len(self.children) < 1 || self.children[0] == nil {
err = self.Errorf("missing the data-source parameter")
func evalFirstChild(ctx ExprContext, iteratorTerm *term) (value any, err error) {
if len(iteratorTerm.children) < 1 || iteratorTerm.children[0] == nil {
err = iteratorTerm.Errorf("missing the data-source parameter")
return
}
value, err = self.children[0].compute(ctx)
value, err = iteratorTerm.children[0].compute(ctx)
return
}
func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map[string]Functor, err error) {
// if dictAny, ok := firstChildValue.(map[any]any); ok {
func getDataSourceDict(iteratorTerm *term, firstChildValue any) (ds map[string]Functor, err error) {
if dictAny, ok := firstChildValue.(*DictType); ok {
requiredFields := []string{currentName, nextName}
fieldsMask := 0b11
requiredFields := []string{NextName}
fieldsMask := 0b1
foundFields := 0
ds = make(map[string]Functor)
for keyAny, item := range *dictAny {
if key, ok := keyAny.(string); ok {
//if functor, ok := item.(*funcDefFunctor); ok {
if functor, ok := item.(Functor); ok {
ds[key] = functor
if index := slices.Index(requiredFields, key); index >= 0 {
@@ -91,57 +72,59 @@ func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map
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
}
func evalIterator(ctx ExprContext, self *term) (v any, err error) {
func evalIterator(ctx ExprContext, opTerm *term) (v any, err error) {
var firstChildValue any
var ds map[string]Functor
if firstChildValue, err = evalFirstChild(ctx, self); err != nil {
if firstChildValue, err = evalFirstChild(ctx, opTerm); err != nil {
return
}
if ds, err = getDataSourceDict(ctx, self, firstChildValue); err != nil {
if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil {
return
}
if ds != nil {
dc := newDataCursor(ctx, ds)
if initFunc, exists := ds[initName]; exists && initFunc != nil {
var dc *dataCursor
dcCtx := ctx.Clone()
if initFunc, exists := ds[InitName]; exists && initFunc != nil {
var args []any
if len(self.children) > 1 {
if args, err = evalTermArray(ctx, self.children[1:]); err != nil {
var resource any
if len(opTerm.children) > 1 {
if args, err = evalTermArray(ctx, opTerm.children[1:]); err != nil {
return
}
} else {
args = []any{}
}
initCtx := dc.ctx.Clone()
if dc.resource, err = initFunc.Invoke(initCtx, initName, args); err != nil {
actualParams := bindActualParams(initFunc, args)
initCtx := ctx.Clone()
if resource, err = initFunc.InvokeNamed(initCtx, InitName, actualParams); err != nil {
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
} else if list, ok := firstChildValue.(*ListType); ok {
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)
}
} else {
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)
}
}
+2 -2
View File
@@ -21,8 +21,8 @@ func newListTerm(row, col int, args []*term) *term {
}
// -------- list func
func evalList(ctx ExprContext, self *term) (v any, err error) {
list, _ := self.value().([]*term)
func evalList(ctx ExprContext, opTerm *term) (v any, err error) {
list, _ := opTerm.value().([]*term)
items := make(ListType, len(list))
for i, tree := range list {
var param any
+2 -2
View File
@@ -17,8 +17,8 @@ func newLiteralTerm(tk *Token) *term {
}
// -------- eval func
func evalLiteral(ctx ExprContext, self *term) (v any, err error) {
v = self.tk.Value
func evalLiteral(ctx ExprContext, opTerm *term) (v any, err error) {
v = opTerm.tk.Value
return
}
+3 -3
View File
@@ -41,10 +41,10 @@ func newSelectorCaseTerm(row, col int, filterList *term, caseExpr Expr) *term {
}
// -------- 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
if v, ok = self.value().(*selectorCase); !ok {
err = fmt.Errorf("selector-case expected, got %T", self.value())
if v, ok = opTerm.value().(*selectorCase); !ok {
err = fmt.Errorf("selector-case expected, got %T", opTerm.value())
}
return
}
+3 -3
View File
@@ -21,11 +21,11 @@ func newVarTerm(tk *Token) *term {
}
// -------- 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
name := self.source()
name := opTerm.source()
if v, exists = GetVar(ctx, name); !exists {
if info, exists, _ := GetFuncInfo(ctx, name); exists {
if info, exists := GetFuncInfo(ctx, name); exists {
v = info.Functor()
} else {
err = fmt.Errorf("undefined variable or function %q", name)
+109 -6
View File
@@ -60,22 +60,23 @@ func assignValue(ctx ExprContext, leftTerm *term, v any) (err error) {
return
}
func evalAssign(ctx ExprContext, self *term) (v any, err error) {
if err = self.checkOperands(); err != nil {
func evalAssign(ctx ExprContext, opTerm *term) (v any, err error) {
if err = opTerm.checkOperands(); err != nil {
return
}
leftTerm := self.children[0]
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", self.tk.source)
err = leftTerm.tk.Errorf("left operand of %q must be a variable or a collection's item", opTerm.tk.source)
return
}
rightChild := self.children[1]
rightChild := opTerm.children[1]
if v, err = rightChild.compute(ctx); err == nil {
if functor, ok := v.(Functor); ok {
if leftSym == SymVariable {
if info := functor.GetFunc(); info != nil {
ctx.RegisterFunc(leftTerm.source(), info.Functor(), info.ReturnType(), info.Params())
} else if funcDef, ok := functor.(*exprFunctor); ok {
@@ -83,7 +84,10 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
ctx.RegisterFunc(leftTerm.source(), functor, TypeAny, paramSpecs)
} else {
err = self.Errorf("unknown function %s()", rightChild.source())
err = opTerm.Errorf("unknown function %s()", rightChild.source())
}
} else {
err = assignValue(ctx, leftTerm, v)
}
} else {
err = assignValue(ctx, leftTerm, v)
@@ -95,7 +99,106 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
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)
default:
err = opTerm.Errorf("unsupported assign operator %q", opTerm.source())
}
if err == nil {
err = assignValue(ctx, leftTerm, v)
}
}
}
return
}
// init
func init() {
registerTermConstructor(SymEqual, newAssignTerm)
registerTermConstructor(SymPlusEqual, newOpAssignTerm)
registerTermConstructor(SymMinusEqual, newOpAssignTerm)
registerTermConstructor(SymStarEqual, newOpAssignTerm)
registerTermConstructor(SymSlashEqual, newOpAssignTerm)
}
+104
View File
@@ -0,0 +1,104 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-binary.go
package expr
//-------- NOT term
func newBinNotTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priBinary,
evalFunc: evalBinaryNot,
}
}
func evalBinaryNot(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
}
//-------- Binary AND term
func newBinAndTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priBinary,
evalFunc: evalBinaryAnd,
}
}
func evalBinaryAnd(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
var leftInt, rightInt int64
var lok, rok bool
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
leftInt, lok = leftValue.(int64)
rightInt, rok = rightValue.(int64)
if lok && rok {
v = leftInt & rightInt
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
//-------- Binary OR term
func newBinOrTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priBinary,
evalFunc: evalBinaryOr,
}
}
func evalBinaryOr(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
var leftInt, rightInt int64
var lok, rok bool
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
leftInt, lok = leftValue.(int64)
rightInt, rok = rightValue.(int64)
if lok && rok {
v = leftInt | rightInt
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
// init
func init() {
registerTermConstructor(SymTilde, newBinNotTerm)
registerTermConstructor(SymAmpersand, newBinAndTerm)
registerTermConstructor(SymVertBar, newBinOrTerm)
}
+5 -9
View File
@@ -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
if rightValue, err = self.evalPrefix(ctx); err != nil {
if rightValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
if b, ok := ToBool(rightValue); ok {
v = !b
} else {
err = self.errIncompatibleType(rightValue)
err = opTerm.errIncompatibleType(rightValue)
}
return
}
@@ -38,8 +38,6 @@ func evalNot(ctx ExprContext, self *term) (v any, err error) {
func newAndTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
// class: classOperator,
// kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priAnd,
@@ -88,7 +86,7 @@ func evalAndWithShortcut(ctx ExprContext, self *term) (v any, err error) {
}
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
} else if !leftBool {
v = false
@@ -107,8 +105,6 @@ func evalAndWithShortcut(ctx ExprContext, self *term) (v any, err error) {
func newOrTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
// class: classOperator,
// kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priOr,
@@ -157,7 +153,7 @@ func evalOrWithShortcut(ctx ExprContext, self *term) (v any, err error) {
}
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
} else if leftBool {
v = true
+4 -4
View File
@@ -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
if childValue, err = self.evalPrefix(ctx); err != nil {
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
@@ -37,11 +37,11 @@ func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
if ImportInContext(module) {
count++
} else {
err = self.Errorf("unknown builtin module %q", module)
err = opTerm.Errorf("unknown builtin module %q", module)
break
}
} else {
err = self.Errorf("expected string at item nr %d, got %s", it.Index()+1, TypeName(moduleSpec))
err = opTerm.Errorf("expected string at item nr %d, got %s", it.Index()+1, TypeName(moduleSpec))
break
}
}
+2 -2
View File
@@ -16,8 +16,8 @@ func newButTerm(tk *Token) (inst *term) {
}
}
func evalBut(ctx ExprContext, self *term) (v any, err error) {
_, v, err = self.evalInfix(ctx)
func evalBut(ctx ExprContext, opTerm *term) (v any, err error) {
_, v, err = opTerm.evalInfix(ctx)
return
}
-85
View File
@@ -1,85 +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,
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 != SymVariable {
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 {
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 != SymVariable {
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)
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, newNullCoalesceTerm)
registerTermConstructor(SymQuestionEqual, newCoalesceAssignTerm)
}
+5 -5
View File
@@ -16,15 +16,15 @@ 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 sourceCtx ExprContext
if self.children == nil || len(self.children) == 0 {
if len(opTerm.children) == 0 {
sourceCtx = ctx
} else if self.children[0].symbol() == SymVariable && self.children[0].source() == "global" {
} else if opTerm.children[0].symbol() == SymVariable && opTerm.children[0].source() == "global" {
sourceCtx = globalCtx
} else if childValue, err = self.evalPrefix(ctx); err == nil {
} else if childValue, err = opTerm.evalPrefix(ctx); err == nil {
if dc, ok := childValue.(*dataCursor); ok {
sourceCtx = dc.ctx
}
@@ -49,7 +49,7 @@ func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
v = d
}
} else {
err = self.errIncompatibleType(childValue)
err = opTerm.errIncompatibleType(childValue)
}
return
}
+1 -1
View File
@@ -16,7 +16,7 @@ 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) {
CtrlEnable(ctx, control_export_all)
return
}
+124
View 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)
}
+7 -7
View File
@@ -15,24 +15,24 @@ func newDotTerm(tk *Token) (inst *term) {
}
}
func evalDot(ctx ExprContext, self *term) (v any, err error) {
func evalDot(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
if err = self.checkOperands(); err != nil {
if err = opTerm.checkOperands(); err != nil {
return
}
if leftValue, err = self.children[0].compute(ctx); err != nil {
if leftValue, err = opTerm.children[0].compute(ctx); err != nil {
return
}
indexTerm := self.children[1]
indexTerm := opTerm.children[1]
switch unboxedValue := leftValue.(type) {
case ExtIterator:
if indexTerm.symbol() == SymVariable /*|| indexTerm.symbol() == SymString */ {
opName := indexTerm.source()
if unboxedValue.HasOperation(opName) {
v, err = unboxedValue.CallOperation(opName, []any{})
v, err = unboxedValue.CallOperation(opName, map[string]any{})
} else {
err = indexTerm.Errorf("this iterator do not support the %q command", opName)
v = false
@@ -41,8 +41,8 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
err = indexTerm.tk.ErrorExpectedGot("identifier")
}
default:
if rightValue, err = self.children[1].compute(ctx); err == nil {
err = self.errIncompatibleTypes(leftValue, rightValue)
if rightValue, err = opTerm.children[1].compute(ctx); err == nil {
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
}
return
+3 -3
View File
@@ -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
if leftValue, err = self.evalPrefix(ctx); err != nil {
if leftValue, err = opTerm.evalPrefix(ctx); err != nil {
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)
}
} else {
err = self.errIncompatibleType(leftValue)
err = opTerm.errIncompatibleType(leftValue)
}
return
}
+8 -3
View File
@@ -24,12 +24,12 @@ func newFractionTerm(tk *Token) *term {
}
// -------- 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 num, den int64
var ok bool
if numValue, denValue, err = self.evalInfix(ctx); err != nil {
if numValue, denValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
if num, ok = numValue.(int64); !ok {
@@ -49,6 +49,7 @@ func evalFraction(ctx ExprContext, self *term) (v any, err error) {
den = -den
num = -num
}
if num != 0 {
g := gcd(num, den)
num = num / g
den = den / g
@@ -57,10 +58,14 @@ func evalFraction(ctx ExprContext, self *term) (v any, err error) {
} else {
v = &FractionType{num, den}
}
} else {
v = &FractionType{0, den}
}
return
}
// init
func init() {
registerTermConstructor(SymVertBar, newFractionTerm)
// registerTermConstructor(SymVertBar, newFractionTerm)
registerTermConstructor(SymColon, newFractionTerm)
}
+3 -3
View File
@@ -21,10 +21,10 @@ func newInTerm(tk *Token) (inst *term) {
// 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
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
@@ -35,7 +35,7 @@ func evalIn(ctx ExprContext, self *term) (v any, err error) {
dict, _ := rightValue.(*DictType)
v = dict.hasKey(leftValue)
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
return
}
+14 -11
View File
@@ -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
if childValue, err = self.evalPrefix(ctx); err != nil {
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
count := 0
if IsList(childValue) {
list, _ := childValue.([]any)
for i, filePathSpec := range list {
list, _ := childValue.(*ListType)
for i, filePathSpec := range *list {
if filePath, ok := filePathSpec.(string); ok {
if v, err = EvalFile(ctx, filePath); err == nil {
count++
} else {
err = self.Errorf("can't load file %q", filePath)
err = opTerm.Errorf("can't load file %q", filePath)
break
}
} 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
}
}
} else if IsString(childValue) {
filePath, _ := childValue.(string)
v, err = EvalFile(ctx, filePath)
} else {
err = self.errIncompatibleType(childValue)
if v, err = EvalFile(ctx, filePath); err == nil {
count++
}
if err == nil {
v = count
} else {
err = opTerm.errIncompatibleType(childValue)
}
if err != nil {
//v = count
v = nil
}
return
}
+12 -24
View File
@@ -15,7 +15,7 @@ func newIndexTerm(tk *Token) (inst *term) {
}
}
func verifyKey(indexTerm *term, indexList *ListType) (index any, err error) {
func verifyKey(indexList *ListType) (index any, err error) {
index = (*indexList)[0]
return
}
@@ -23,7 +23,7 @@ func verifyKey(indexTerm *term, indexList *ListType) (index any, err error) {
func verifyIndex(indexTerm *term, indexList *ListType, maxValue int) (index int, err error) {
var v int
if v, err = ToInt((*indexList)[0], "index expression"); err == nil {
if v, err = ToGoInt((*indexList)[0], "index expression"); err == nil {
if v < 0 && v >= -maxValue {
v = maxValue + v
}
@@ -59,18 +59,18 @@ func verifyRange(indexTerm *term, indexList *ListType, maxValue int) (startIndex
return
}
func evalIndex(ctx ExprContext, self *term) (v any, err error) {
func evalIndex(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
var indexList *ListType
var ok bool
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
indexTerm := self.children[1]
indexTerm := opTerm.children[1]
if indexList, ok = rightValue.(*ListType); !ok {
err = self.Errorf("invalid index expression")
err = opTerm.Errorf("invalid index expression")
return
} else if len(*indexList) != 1 {
err = indexTerm.Errorf("one index only is allowed")
@@ -90,16 +90,9 @@ func evalIndex(ctx ExprContext, self *term) (v any, err error) {
v = string(unboxedValue[index])
}
case *DictType:
/* var ok bool
var indexValue any
if indexValue, err = verifyKey(indexTerm, indexList); err == nil {
if v, ok = (*unboxedValue)[indexValue]; !ok {
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
}
} */
v, err = getDictItem(unboxedValue, indexTerm, indexList, rightValue)
default:
err = self.errIncompatibleTypes(leftValue, rightValue)
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
} else if isIntPair((*indexList)[0]) {
switch unboxedValue := leftValue.(type) {
@@ -115,19 +108,14 @@ func evalIndex(ctx ExprContext, self *term) (v any, err error) {
v = unboxedValue[start:end]
}
default:
err = self.errIncompatibleTypes(leftValue, rightValue)
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
} else if IsDict(leftValue) {
d := leftValue.(*DictType)
/* var ok bool
var indexValue any
if indexValue, err = verifyKey(indexTerm, indexList); err == nil {
if v, ok = (*d)[indexValue]; !ok {
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
}
}*/
v, err = getDictItem(d, indexTerm, indexList, rightValue)
} else {
rightChild := opTerm.children[1]
err = rightChild.Errorf("invalid index type: %v", (*indexList)[0])
}
return
}
@@ -136,7 +124,7 @@ func getDictItem(d *DictType, indexTerm *term, indexList *ListType, rightValue a
var ok bool
var indexValue any
if indexValue, err = verifyKey(indexTerm, indexList); err == nil {
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)
}
+18 -10
View File
@@ -26,10 +26,10 @@ func newAppendTerm(tk *Token) (inst *term) {
}
}
func evalInsert(ctx ExprContext, self *term) (v any, err error) {
func evalInsert(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
@@ -37,19 +37,23 @@ func evalInsert(ctx ExprContext, self *term) (v any, err error) {
list, _ := rightValue.(*ListType)
newList := append(ListType{leftValue}, *list...)
v = &newList
if self.children[1].symbol() == SymVariable {
ctx.UnsafeSetVar(self.children[1].source(), v)
if opTerm.children[1].symbol() == SymVariable {
ctx.UnsafeSetVar(opTerm.children[1].source(), v)
}
} else if IsInteger(leftValue) && IsInteger(rightValue) {
leftInt := leftValue.(int64)
rightInt := rightValue.(int64)
v = leftInt >> rightInt
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
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
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
@@ -57,11 +61,15 @@ func evalAppend(ctx ExprContext, self *term) (v any, err error) {
list, _ := leftValue.(*ListType)
newList := append(*list, rightValue)
v = &newList
if self.children[0].symbol() == SymVariable {
ctx.UnsafeSetVar(self.children[0].source(), v)
if opTerm.children[0].symbol() == SymVariable {
ctx.UnsafeSetVar(opTerm.children[0].source(), v)
}
} else if IsInteger(leftValue) && IsInteger(rightValue) {
leftInt := leftValue.(int64)
rightInt := rightValue.(int64)
v = leftInt << rightInt
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
return
}
+5 -4
View File
@@ -16,22 +16,23 @@ func newIterValueTerm(tk *Token) (inst *term) {
}
}
func evalIterValue(ctx ExprContext, self *term) (v any, err error) {
func evalIterValue(ctx ExprContext, opTerm *term) (v any, err error) {
var childValue any
if childValue, err = self.evalPrefix(ctx); err != nil {
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
if it, ok := childValue.(Iterator); ok {
v, err = it.Current()
} else {
err = self.errIncompatibleType(childValue)
err = opTerm.errIncompatibleType(childValue)
}
return
}
// init
func init() {
registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
// registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
registerTermConstructor(SymCaret, newIterValueTerm)
}
+10 -10
View File
@@ -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
if childValue, err = self.evalPrefix(ctx); err != nil {
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
@@ -30,18 +30,18 @@ func evalLength(ctx ExprContext, self *term) (v any, err error) {
s, _ := childValue.(string)
v = int64(len(s))
} else if IsDict(childValue) {
// m, _ := childValue.(map[any]any)
m, _ := childValue.(*DictType)
v = int64(len(*m))
} else if it, ok := childValue.(Iterator); ok {
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) {
count, _ := extIt.CallOperation(countName, nil)
v, _ = ToInt(count, "")
v = int64(it.Count())
// if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(CountName) {
// count, _ := extIt.CallOperation(CountName, nil)
// v, _ = ToGoInt(count, "")
// } else {
// v = int64(it.Index() + 1)
// }
} else {
v = int64(it.Index() + 1)
}
} else {
err = self.errIncompatibleType(childValue)
err = opTerm.errIncompatibleType(childValue)
}
return
}
+4 -26
View File
@@ -4,10 +4,6 @@
// operator-plugin.go
package expr
import (
"io"
)
//-------- plugin term
func newPluginTerm(tk *Token) (inst *term) {
@@ -20,33 +16,15 @@ func newPluginTerm(tk *Token) (inst *term) {
}
}
func evalPlugin(ctx ExprContext, self *term) (v any, err error) {
func evalPlugin(ctx ExprContext, opTerm *term) (v any, err error) {
var childValue any
var moduleSpec any
var count int
if childValue, err = self.evalPrefix(ctx); err != nil {
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
dirList := buildSearchDirList("plugin", ENV_EXPR_PLUGIN_PATH)
count := 0
it := NewAnyIterator(childValue)
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
if module, ok := moduleSpec.(string); ok {
if err = importPlugin(dirList, module); err != nil {
break
}
count++
} else {
err = self.Errorf("expected string as item nr %d, got %s", it.Index()+1, TypeName(moduleSpec))
break
}
}
if err == io.EOF {
err = nil
}
if err == nil {
if count, err = importPluginFromSearchPath(childValue); err == nil {
v = int64(count)
}
return
+37 -5
View File
@@ -17,20 +17,51 @@ func newPostIncTerm(tk *Token) *term {
}
}
func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
func evalPostInc(ctx ExprContext, opTerm *term) (v any, err error) {
var childValue any
if childValue, err = self.evalPrefix(ctx); err != nil {
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
if it, ok := childValue.(Iterator); ok {
v, err = it.Next()
} else if IsInteger(childValue) && self.children[0].symbol() == SymVariable {
} else if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
v = childValue
i, _ := childValue.(int64)
ctx.SetVar(self.children[0].source(), i+1)
ctx.SetVar(opTerm.children[0].source(), i+1)
} else {
err = self.errIncompatibleType(childValue)
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
}
@@ -38,4 +69,5 @@ func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
// init
func init() {
registerTermConstructor(SymDoublePlus, newPostIncTerm)
registerTermConstructor(SymDoubleMinus, newPostDecTerm)
}
+42 -37
View File
@@ -14,8 +14,6 @@ import (
func newMultiplyTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
// class: classOperator,
// kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priProduct,
@@ -23,13 +21,7 @@ func newMultiplyTerm(tk *Token) (inst *term) {
}
}
func evalMultiply(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
func mulValues(opTerm *term, leftValue, rightValue any) (v any, err error) {
if IsString(leftValue) && IsInteger(rightValue) {
s, _ := leftValue.(string)
n, _ := rightValue.(int64)
@@ -45,18 +37,26 @@ func evalMultiply(ctx ExprContext, self *term) (v any, err error) {
v = leftInt * rightInt
}
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
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
func newDivideTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
// class: classOperator,
// kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priProduct,
@@ -64,13 +64,7 @@ func newDivideTerm(tk *Token) (inst *term) {
}
}
func evalDivide(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
func divValues(opTerm *term, leftValue, rightValue any) (v any, err error) {
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) {
d := numAsFloat(rightValue)
@@ -90,11 +84,21 @@ func evalDivide(ctx ExprContext, self *term) (v any, err error) {
}
}
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
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
func newDivideAsFloatTerm(tk *Token) (inst *term) {
@@ -107,10 +111,10 @@ 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
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
if leftValue, rightValue, err = floatDivTerm.evalInfix(ctx); err != nil {
return
}
@@ -122,32 +126,23 @@ func evalDivideAsFloat(ctx ExprContext, self *term) (v any, err error) {
v = numAsFloat(leftValue) / d
}
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
err = floatDivTerm.errIncompatibleTypes(leftValue, rightValue)
}
return
}
//-------- reminder term
func newReminderTerm(tk *Token) (inst *term) {
func newRemainderTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
// class: classOperator,
// kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priProduct,
evalFunc: evalReminder,
evalFunc: evalRemainder,
}
}
func evalReminder(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
func remainderValues(opTerm *term, leftValue, rightValue any) (v any, err error) {
if IsInteger(leftValue) && IsInteger(rightValue) {
rightInt, _ := rightValue.(int64)
if rightInt == 0 {
@@ -157,15 +152,25 @@ func evalReminder(ctx ExprContext, self *term) (v any, err error) {
v = leftInt % rightInt
}
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
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
func init() {
registerTermConstructor(SymStar, newMultiplyTerm)
registerTermConstructor(SymSlash, newDivideTerm)
registerTermConstructor(SymDotSlash, newDivideAsFloatTerm)
registerTermConstructor(SymPercent, newReminderTerm)
registerTermConstructor(SymPercent, newRemainderTerm)
}
+23 -10
View File
@@ -34,25 +34,30 @@ func newRangeTerm(tk *Token) (inst *term) {
}
}
func evalRange(ctx ExprContext, self *term) (v any, err error) {
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 err = self.checkOperands(); err != nil {
// return
// }
if len(self.children) == 0 {
if len(opTerm.children) == 0 {
leftValue = int64(0)
rightValue = int64(-1)
} else if len(self.children) == 1 {
if leftValue, err = self.children[0].compute(ctx); err != nil {
} 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 = self.evalInfix(ctx); err != nil {
} else if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
if !(IsInteger(leftValue) && IsInteger(rightValue)) {
err = self.errIncompatibleTypes(leftValue, rightValue)
// err = opTerm.errIncompatibleTypes(leftValue, rightValue)
err = errRangeInvalidSpecification(opTerm)
return
}
@@ -63,7 +68,15 @@ func evalRange(ctx ExprContext, self *term) (v any, err error) {
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(SymColon, newRangeTerm)
registerTermConstructor(SymRange, newRangeTerm)
}
+16 -16
View File
@@ -45,10 +45,10 @@ func equals(a, b any, deepCmp deepFuncTemplate) (eq bool, err error) {
return
}
func evalEqual(ctx ExprContext, self *term) (v any, err error) {
func evalEqual(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
@@ -68,8 +68,8 @@ func newNotEqualTerm(tk *Token) (inst *term) {
}
}
func evalNotEqual(ctx ExprContext, self *term) (v any, err error) {
if v, err = evalEqual(ctx, self); err == nil {
func evalNotEqual(ctx ExprContext, opTerm *term) (v any, err error) {
if v, err = evalEqual(ctx, opTerm); err == nil {
b, _ := ToBool(v)
v = !b
}
@@ -119,13 +119,13 @@ func lessThan(self *term, a, b any) (isLess bool, err error) {
return
}
func evalLess(ctx ExprContext, self *term) (v any, err error) {
func evalLess(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
v, err = lessThan(self, leftValue, rightValue)
v, err = lessThan(opTerm, leftValue, rightValue)
return
}
@@ -154,14 +154,14 @@ func lessThanOrEqual(self *term, a, b any) (isLessEq bool, err error) {
return
}
func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
func evalLessEqual(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
v, err = lessThanOrEqual(self, leftValue, rightValue)
v, err = lessThanOrEqual(opTerm, leftValue, rightValue)
return
}
@@ -178,14 +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) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
v, err = lessThan(self, rightValue, leftValue)
v, err = lessThan(opTerm, rightValue, leftValue)
return
}
@@ -201,14 +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) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return
}
v, err = lessThanOrEqual(self, rightValue, leftValue)
v, err = lessThanOrEqual(opTerm, rightValue, leftValue)
return
}
+15 -5
View File
@@ -22,9 +22,19 @@ func trySelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (ma
selectedValue, err = caseData.caseExpr.Eval(ctx)
match = true
} else if filterList, ok := caseData.filterList.value().([]*term); ok {
if len(filterList) == 0 && exprValue == int64(caseIndex) {
if len(filterList) == 0 {
var valueAsInt = int64(0)
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 {
var caseValue any
for _, caseTerm := range filterList {
@@ -39,19 +49,19 @@ func trySelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (ma
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 match bool
if err = self.checkOperands(); err != nil {
if err = opTerm.checkOperands(); err != nil {
return
}
exprTerm := self.children[0]
exprTerm := opTerm.children[0]
if exprValue, err = exprTerm.compute(ctx); err != nil {
return
}
caseListTerm := self.children[1]
caseListTerm := opTerm.children[1]
caseList, _ := caseListTerm.value().([]*term)
for i, caseTerm := range caseList {
caseSel := caseTerm.value()
+5 -5
View File
@@ -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
if rightValue, err = self.evalPrefix(ctx); err != nil {
if rightValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
if IsFloat(rightValue) {
if self.tk.Sym == SymChangeSign {
if opTerm.tk.Sym == SymChangeSign {
f, _ := rightValue.(float64)
v = -f
} else {
v = rightValue
}
} else if IsInteger(rightValue) {
if self.tk.Sym == SymChangeSign {
if opTerm.tk.Sym == SymChangeSign {
i, _ := rightValue.(int64)
v = -i
} else {
v = rightValue
}
} else {
err = self.errIncompatibleType(rightValue)
err = opTerm.errIncompatibleType(rightValue)
}
return
}
+28 -22
View File
@@ -21,13 +21,7 @@ func newPlusTerm(tk *Token) (inst *term) {
}
}
func evalPlus(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
func sumValues(plusTerm *term, leftValue, rightValue any) (v any, err error) {
if (IsString(leftValue) && isNumberString(rightValue)) || (IsString(rightValue) && isNumberString(leftValue)) {
v = fmt.Sprintf("%v%v", leftValue, rightValue)
} else if IsNumber(leftValue) && IsNumber(rightValue) {
@@ -44,12 +38,8 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
rightList, _ = rightValue.(*ListType)
sumList := make(ListType, 0, len(*leftList)+len(*rightList))
for _, item := range *leftList {
sumList = append(sumList, item)
}
for _, item := range *rightList {
sumList = append(sumList, item)
}
sumList = append(sumList, *leftList...)
sumList = append(sumList, *rightList...)
v = &sumList
} else if (isFraction(leftValue) && IsNumber(rightValue)) || (isFraction(rightValue) && IsNumber(leftValue)) {
if IsFloat(leftValue) || IsFloat(rightValue) {
@@ -63,12 +53,24 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
c := leftDict.clone()
c.merge(rightDict)
v = c
} else if isFraction(leftValue) && isFraction(rightValue) {
v, err = sumAnyFract(leftValue, rightValue)
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
err = plusTerm.errIncompatibleTypes(leftValue, rightValue)
}
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
func newMinusTerm(tk *Token) (inst *term) {
@@ -81,13 +83,7 @@ func newMinusTerm(tk *Token) (inst *term) {
}
}
func evalMinus(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
func diffValues(minusTerm *term, leftValue, rightValue any) (v any, err error) {
if isNumOrFract(leftValue) && isNumOrFract(rightValue) {
if IsFloat(leftValue) || IsFloat(rightValue) {
v = numAsFloat(leftValue) - numAsFloat(rightValue)
@@ -109,11 +105,21 @@ func evalMinus(ctx ExprContext, self *term) (v any, err error) {
}
v = &diffList
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
err = minusTerm.errIncompatibleTypes(leftValue, rightValue)
}
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
func init() {
registerTermConstructor(SymPlus, newPlusTerm)
+69
View 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)
}
+167 -89
View File
@@ -6,10 +6,46 @@ package expr
import (
"errors"
"golang.org/x/exp/constraints"
)
//-------- parser
type parserContext uint16
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 addFlags[T constraints.Unsigned](set T, flags T) T {
return set | flags
}
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 {
}
@@ -18,13 +54,19 @@ func NewParser() (p *parser) {
return p
}
func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token) (tree *term, err error) {
func (parser *parser) Next(scanner *scanner) (tk *Token) {
for tk = scanner.Next(); tk.IsSymbol(SymComment); tk = scanner.Next() {
}
return
}
func (parser *parser) parseFuncCall(scanner *scanner, ctx parserContext, tk *Token) (tree *term, err error) {
args := make([]*term, 0, 10)
itemExpected := false
lastSym := SymUnknown
for lastSym != SymClosedRound && lastSym != SymEos {
var subTree *ast
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err != nil {
if subTree, err = parser.parseItem(scanner, ctx, SymComma, SymClosedRound); err != nil {
break
}
prev := scanner.Previous()
@@ -48,7 +90,7 @@ func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token)
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)
@@ -57,15 +99,21 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
itemExpected := false
tk := scanner.Previous()
for lastSym != SymClosedRound && lastSym != SymEos {
tk = scanner.Next()
tk = parser.Next(scanner)
if tk.IsSymbol(SymIdentifier) {
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)
tk = scanner.Next()
tk = parser.Next(scanner)
if tk.Sym == SymEqual {
var paramExpr *ast
defaultParamsStarted = true
if paramExpr, err = self.parseItem(scanner, false, SymComma, SymClosedRound); err != nil {
if paramExpr, err = parser.parseItem(scanner, parserNoFlags, SymComma, SymClosedRound); err != nil {
break
}
param.forceChild(paramExpr.root)
@@ -86,9 +134,9 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
err = tk.ErrorExpectedGot(")")
}
if err == nil {
tk = scanner.Next()
tk = parser.Next(scanner)
if tk.IsSymbol(SymOpenBrace) {
body, err = self.parseGeneral(scanner, true, true, SymClosedBrace)
body, err = parser.parseGeneral(scanner, allowMultiExpr|allowVarRef, SymClosedBrace)
} else {
err = tk.ErrorExpectedGot("{")
}
@@ -104,27 +152,43 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
return
}
func (self *parser) parseList(scanner *scanner, parsingIndeces bool, 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)
lastSym := SymUnknown
itemExpected := false
itemCtx := remFlags(ctx, allowIndex)
for lastSym != SymClosedSquare && lastSym != SymEos {
var subTree *ast
zeroRequired := scanner.current.Sym == SymColon
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
root := subTree.root
var itemTree *ast
if itemTree, err = parser.parseItem(scanner, itemCtx, SymComma, SymClosedSquare); err == nil {
root := itemTree.root
if root != nil {
if !parsingIndeces && root.symbol() == SymColon {
err = root.Errorf("unexpected range expression")
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 parsingIndeces && root.symbol() == SymColon && zeroRequired { //len(root.children) == 0 {
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 = root.Errorf("invalid range specification")
err = errRangeInvalidSpecification(root)
break
}
zeroTk := NewValueToken(root.tk.row, root.tk.col, SymInteger, "0", int64(0))
@@ -141,26 +205,28 @@ func (self *parser) parseList(scanner *scanner, parsingIndeces bool, allowVarRef
break
}
lastSym = scanner.Previous().Sym
itemExpected = lastSym == SymComma
if itemExpected = lastSym == SymComma; itemExpected {
remFlags(ctx, allowIndex)
}
}
if err == nil {
if lastSym != SymClosedSquare {
err = scanner.Previous().ErrorExpectedGot("]")
} else {
subtree = newListTerm(r, c, args)
listTerm = newListTerm(r, c, args)
}
}
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()
args := make([]*term, 0)
lastSym := SymUnknown
itemExpected := false
for lastSym != SymClosedRound && lastSym != SymEos {
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 if itemExpected {
@@ -184,8 +250,8 @@ func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *t
return
}
func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, err error) {
tk := scanner.Next()
func (parser *parser) parseDictKey(scanner *scanner) (key any, err error) {
tk := parser.Next(scanner)
if tk.Sym == SymError {
err = tk.Error()
return
@@ -194,7 +260,7 @@ func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, e
return
}
if tk.Sym == SymInteger || tk.Sym == SymString {
tkSep := scanner.Next()
tkSep := parser.Next(scanner)
if tkSep.Sym != SymColon {
err = tkSep.ErrorExpectedGot(":")
} else {
@@ -206,14 +272,14 @@ func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, e
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)
lastSym := SymUnknown
itemExpected := false
for lastSym != SymClosedBrace && lastSym != SymEos {
var subTree *ast
var key any
if key, err = self.parseDictKey(scanner, allowVarRef); err != nil {
if key, err = parser.parseDictKey(scanner); err != nil {
break
} else if key == nil {
tk := scanner.Previous()
@@ -223,10 +289,10 @@ func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree
}
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 {
args[key] = subTree.root
} else if key != nil {
} else /*if key != nil*/ {
prev := scanner.Previous()
err = prev.ErrorExpectedGot("dictionary-value")
break
@@ -242,16 +308,16 @@ func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree
err = scanner.Previous().ErrorExpectedGot("}")
} else {
subtree = newDictTerm(args)
// subtree = newMapTerm(args)
}
}
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 caseExpr *ast
tk := scanner.Next()
ctx = remFlags(ctx, allowIndex)
tk := parser.Next(scanner)
startRow := tk.row
startCol := tk.col
if tk.Sym == SymOpenSquare {
@@ -259,10 +325,10 @@ func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaul
err = tk.Errorf("case list in default clause")
return
}
if filterList, err = self.parseList(scanner, false, allowVarRef); err != nil {
if filterList, err = parser.parseList(scanner, remFlags(ctx, allowIndex)); err != nil {
return
}
tk = scanner.Next()
tk = parser.Next(scanner)
startRow = tk.row
startCol = tk.col
} else if !defaultCase {
@@ -270,7 +336,7 @@ func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaul
}
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
}
} else {
@@ -296,25 +362,28 @@ func addSelectorCase(selectorTerm, caseTerm *term) {
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
ctx = remFlags(ctx, allowIndex)
tk := scanner.makeToken(SymSelector, '?')
if selectorTerm, err = tree.addToken2(tk); err != nil {
if selectorTerm, err = tree.addToken(tk); err != nil {
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)
}
return
}
func (self *parser) parseItem(scanner *scanner, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
return self.parseGeneral(scanner, false, allowVarRef, termSymbols...)
func (parser *parser) parseItem(scanner *scanner, ctx parserContext, termSymbols ...Symbol) (tree *ast, err error) {
return parser.parseGeneral(scanner, ctx|allowVarRef, termSymbols...)
}
func (self *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, err error) {
return self.parseGeneral(scanner, true, false, termSymbols...)
func (parser *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, err error) {
termSymbols = append(termSymbols, SymEos)
return parser.parseGeneral(scanner, allowMultiExpr, termSymbols...)
}
func couldBeACollection(t *term) bool {
@@ -325,28 +394,36 @@ func couldBeACollection(t *term) bool {
return sym == SymList || sym == SymString || sym == SymDict || sym == SymExpression || sym == SymVariable
}
// func areSymbolsOutOfCtx(tk *Token, ctxTerm *term, syms ...Symbol) bool {
// var areOut = false
// if ctxTerm != nil {
// areOut = tk.IsOneOf(syms)
// }
// return areOut
// }
func 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 (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
func (parser *parser) parseGeneral(scanner *scanner, ctx parserContext, termSymbols ...Symbol) (tree *ast, err error) {
var selectorTerm *term = nil
var currentTerm *term = nil
var tk *Token
tree = NewAst()
firstToken := true
// lastSym := SymUnknown
for tk = scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); /*&& !areSymbolsOutOfCtx(tk, selectorTerm, SymColon, SymDoubleColon)*/ tk = scanner.Next() {
if tk.Sym == SymComment {
continue
}
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 {
// continue
// }
if tk.Sym == SymSemiColon {
if allowForest {
if hasFlag(ctx, allowMultiExpr) {
tree.ToForest()
firstToken = true
currentTerm = nil
@@ -364,6 +441,9 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
tk.Sym = SymChangeSign
} else if tk.Sym == SymPlus {
tk.Sym = SymUnchangeSign
} else if tk.IsSymbol(SymExclamation) {
err = tk.Errorf("postfix opertor %q requires an operand on its left", tk)
break
}
firstToken = false
}
@@ -371,72 +451,63 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
switch tk.Sym {
case SymOpenRound:
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
err = tree.addTerm(newExprTerm(subTree.root))
currentTerm = subTree.root
}
case SymFuncCall:
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)
currentTerm = funcCallTerm
}
case SymOpenSquare:
var listTerm *term
parsingIndeces := couldBeACollection(currentTerm)
if listTerm, err = self.parseList(scanner, parsingIndeces, allowVarRef); err == nil {
if parsingIndeces {
indexTk := NewToken(listTerm.tk.row, listTerm.tk.col, SymIndex, listTerm.source())
indexTerm := newTerm(indexTk)
if err = tree.addTerm(indexTerm); err == nil {
err = tree.addTerm(listTerm)
}
} else {
err = tree.addTerm(listTerm)
}
currentTerm = listTerm
newCtx := addFlagsCond(addFlags(ctx, squareContext), allowIndex, couldBeACollection(currentTerm))
if listTerm, err = parser.parseList(scanner, newCtx); err == nil {
currentTerm, err = listSubTree(tree, listTerm, hasFlag(newCtx, allowIndex))
}
case SymOpenBrace:
if currentTerm != nil && currentTerm.symbol() == SymColon {
err = currentTerm.Errorf(`selector-case outside of a selector context`)
} else {
var mapTerm *term
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
if mapTerm, err = parser.parseDictionary(scanner, ctx); err == nil {
err = tree.addTerm(mapTerm)
currentTerm = mapTerm
}
}
case SymEqual:
// if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
currentTerm, err = tree.addToken2(tk)
// }
case SymEqual, SymPlusEqual, SymMinusEqual, SymStarEqual, SymSlashEqual, SymPercEqual:
currentTerm, err = tree.addToken(tk)
firstToken = true
case SymFuncDef:
var funcDefTerm *term
if funcDefTerm, err = self.parseFuncDef(scanner); err == nil {
if funcDefTerm, err = parser.parseFuncDef(scanner); err == nil {
err = tree.addTerm(funcDefTerm)
currentTerm = funcDefTerm
}
case SymDollarRound:
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)
currentTerm = iterDefTerm
}
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)
} else {
currentTerm, err = tree.addToken2(tk)
currentTerm, err = tree.addToken(tk)
}
case SymQuestion:
if selectorTerm, err = self.parseSelector(scanner, tree, allowVarRef); err == nil {
if selectorTerm, err = parser.parseSelector(scanner, tree, ctx); err == nil {
currentTerm = selectorTerm
addFlags(ctx, selectorContext)
}
case SymColon, SymDoubleColon:
var caseTerm *term
if selectorTerm != nil {
if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
if caseTerm, err = parser.parseSelectorCase(scanner, ctx, tk.Sym == SymDoubleColon); err == nil {
addSelectorCase(selectorTerm, caseTerm)
currentTerm = caseTerm
if tk.Sym == SymDoubleColon {
@@ -444,31 +515,38 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
}
}
} else {
currentTerm, err = tree.addToken2(tk)
// if hasFlag(ctx, allowIndex) {
// tk.Sym = SymRange
// }
currentTerm, err = tree.addToken(tk)
}
if tk.IsSymbol(SymColon) {
if tk.IsOneOfA(SymColon, SymRange) {
// Colon outside a selector term acts like a separator
firstToken = true
}
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 {
selectorTerm = nil
remFlags(ctx, selectorContext)
}
// lastSym = tk.Sym
}
if err == nil {
if !tk.IsOneOf(termSymbols) {
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
}
// func checkPrevSymbol(lastSym, wantedSym Symbol, tk *Token) (err error) {
// if lastSym != wantedSym {
// err = fmt.Errorf(`assign operator (%q) must be preceded by a variable`, tk.source)
// }
// return
// }
+25 -1
View File
@@ -6,6 +6,7 @@ package expr
import (
"fmt"
"io"
"os"
"plugin"
"strings"
@@ -29,7 +30,7 @@ func pluginExists(name string) (exists bool) {
func makePluginName(name string) (decorated string) {
var template string
if execName, err := os.Executable(); err != nil || strings.Index(execName, "debug") < 0 {
if execName, err := os.Executable(); err != nil || !strings.Contains(execName, "debug") {
template = "expr-%s-plugin.so"
} else {
template = "expr-%s-plugin.so.debug"
@@ -90,6 +91,29 @@ func importPlugin( /*ctx ExprContext,*/ dirList []string, name string) (err erro
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 {
+210 -188
View File
@@ -16,6 +16,7 @@ import (
type scanner struct {
current *Token
prev *Token
stage *Token
stream *bufio.Reader
row int
column int
@@ -39,7 +40,7 @@ func DefaultTranslations() map[Symbol]Symbol {
SymKwAnd: SymAnd,
SymDoubleVertBar: SymOr,
SymKwOr: SymOr,
SymTilde: SymNot,
// SymTilde: SymNot,
SymKwNot: SymNot,
SymLessGreater: SymNotEqual,
}
@@ -49,255 +50,276 @@ func DefaultTranslations() map[Symbol]Symbol {
// return self.current
// }
func (self *scanner) readChar() (ch byte, err error) {
if ch, err = self.stream.ReadByte(); err == nil {
func (scanner *scanner) readChar() (ch byte, err error) {
if ch, err = scanner.stream.ReadByte(); err == nil {
if ch == '\n' {
self.row++
self.column = 0
scanner.row++
scanner.column = 0
} else {
self.column++
scanner.column++
}
}
return
}
func (self *scanner) unreadChar() (err error) {
if err = self.stream.UnreadByte(); err == nil {
if self.column--; self.column == 0 {
if self.row--; self.row == 0 {
func (scanner *scanner) unreadChar() (err error) {
if err = scanner.stream.UnreadByte(); err == nil {
if scanner.column--; scanner.column == 0 {
if scanner.row--; scanner.row == 0 {
err = errors.New("unread beyond the stream boundary")
} else {
self.column = 1
scanner.column = 1
}
}
}
return
}
func (self *scanner) lastPos() (r, c int) {
if self.prev != nil {
r = self.prev.row
c = self.prev.col
func (scanner *scanner) UnreadToken() (err error) {
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) Previous() *Token {
return self.prev
func (scanner *scanner) lastPos() (r, c int) {
if scanner.prev != nil {
r = scanner.prev.row
c = scanner.prev.col
}
return
}
func (self *scanner) Next() (tk *Token) {
self.prev = self.current
tk = self.current
self.current = self.fetchNextToken()
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
}
func (self *scanner) fetchNextToken() (tk *Token) {
func (scanner *scanner) fetchNextToken() (tk *Token) {
var ch byte
if err := self.skipBlanks(); err != nil {
return self.makeErrorToken(err)
if err := scanner.skipBlanks(); err != nil {
return scanner.makeErrorToken(err)
}
escape := false
for {
ch, _ = self.readChar()
ch, _ = scanner.readChar()
switch ch {
case '+':
if next, _ := self.peek(); next == '+' {
tk = self.moveOn(SymDoublePlus, ch, next)
if next, _ := scanner.peek(); next == '+' {
tk = scanner.moveOn(SymDoublePlus, ch, next)
} else if next == '=' {
tk = self.moveOn(SymPlusEqual, ch, next)
tk = scanner.moveOn(SymPlusEqual, ch, next)
} else {
tk = self.makeToken(SymPlus, ch)
tk = scanner.makeToken(SymPlus, ch)
}
case '-':
if next, _ := self.peek(); next == '-' {
tk = self.moveOn(SymDoubleMinus, ch, next)
if next, _ := scanner.peek(); next == '-' {
tk = scanner.moveOn(SymDoubleMinus, ch, next)
} else if next == '=' {
tk = self.moveOn(SymMinusEqual, ch, next)
tk = scanner.moveOn(SymMinusEqual, ch, next)
} else {
tk = self.makeToken(SymMinus, ch)
tk = scanner.makeToken(SymMinus, ch)
}
case '*':
if next, _ := self.peek(); next == '*' {
tk = self.moveOn(SymDoubleStar, ch, next)
if next, _ := scanner.peek(); next == '*' {
tk = scanner.moveOn(SymDoubleStar, ch, next)
// } else if next == '/' {
// tk = self.moveOn(SymClosedComment, ch, next)
} else if next, _ = scanner.peek(); next == '=' {
tk = scanner.moveOn(SymStarEqual, ch, next)
} else {
tk = self.makeToken(SymStar, ch)
tk = scanner.makeToken(SymStar, ch)
}
case '/':
if next, _ := self.peek(); next == '*' {
self.readChar()
tk = self.fetchBlockComment()
if next, _ := scanner.peek(); next == '*' {
scanner.readChar()
tk = scanner.fetchBlockComment()
} else if next, _ = scanner.peek(); next == '=' {
tk = scanner.moveOn(SymSlashEqual, ch, next)
} else if next == '/' {
self.readChar()
tk = self.fetchOnLineComment()
scanner.readChar()
tk = scanner.fetchOnLineComment()
} else {
tk = self.makeToken(SymSlash, ch)
tk = scanner.makeToken(SymSlash, ch)
}
case '\\':
if escape {
tk = self.makeToken(SymBackSlash, ch)
tk = scanner.makeToken(SymBackSlash, ch)
escape = false
} else {
escape = true
}
case '|':
if next, _ := self.peek(); next == '|' {
tk = self.moveOn(SymDoubleVertBar, ch, next)
if next, _ := scanner.peek(); next == '|' {
tk = scanner.moveOn(SymDoubleVertBar, ch, next)
} else {
tk = self.makeToken(SymVertBar, ch)
tk = scanner.makeToken(SymVertBar, ch)
}
case ',':
tk = self.makeToken(SymComma, ch)
tk = scanner.makeToken(SymComma, ch)
case '^':
tk = self.makeToken(SymCaret, ch)
tk = scanner.makeToken(SymCaret, ch)
case ':':
if next, _ := self.peek(); next == ':' {
tk = self.moveOn(SymDoubleColon, ch, next)
if next, _ := scanner.peek(); next == ':' {
tk = scanner.moveOn(SymDoubleColon, ch, next)
} else {
tk = self.makeToken(SymColon, ch)
tk = scanner.makeToken(SymColon, ch)
}
case ';':
tk = self.makeToken(SymSemiColon, ch)
tk = scanner.makeToken(SymSemiColon, ch)
case '.':
//if next, _ := self.peek(); next >= '0' && next <= '9' {
// tk = self.parseNumber(ch)
//} else if next == '/' {
if next, _ := self.peek(); next == '/' {
tk = self.moveOn(SymDotSlash, ch, next)
if next, _ := scanner.peek(); next == '/' {
tk = scanner.moveOn(SymDotSlash, ch, next)
} else if next == '.' {
if next1, _ := self.peek(); next1 == '.' {
tk = self.moveOn(SymTripleDot, ch, next, next1)
if next1, _ := scanner.peek(); next1 == '.' {
tk = scanner.moveOn(SymTripleDot, ch, next, next1)
} else {
tk = self.moveOn(SymDoubleDot, ch, next)
tk = scanner.moveOn(SymDoubleDot, ch, next)
}
} else {
tk = self.makeToken(SymDot, ch)
tk = scanner.makeToken(SymDot, ch)
}
case '\'':
if escape {
tk = self.makeToken(SymQuote, ch)
tk = scanner.makeToken(SymQuote, ch)
escape = false
} else {
tk = self.fetchString(ch)
tk = scanner.fetchString(ch)
}
case '"':
if escape {
tk = self.makeToken(SymDoubleQuote, ch)
tk = scanner.makeToken(SymDoubleQuote, ch)
escape = false
} else {
tk = self.fetchString(ch)
tk = scanner.fetchString(ch)
}
case '`':
tk = self.makeToken(SymBackTick, ch)
tk = scanner.makeToken(SymBackTick, ch)
case '!':
if next, _ := self.peek(); next == '=' {
tk = self.moveOn(SymNotEqual, ch, next)
if next, _ := scanner.peek(); next == '=' {
tk = scanner.moveOn(SymNotEqual, ch, next)
} else {
tk = self.makeToken(SymExclamation, ch)
tk = scanner.makeToken(SymExclamation, ch)
}
case '?':
if next, _ := self.peek(); next == '?' {
tk = self.moveOn(SymDoubleQuestion, ch, next)
} else if next, _ := self.peek(); next == '=' {
tk = self.moveOn(SymQuestionEqual, ch, next)
if next, _ := scanner.peek(); next == '?' {
tk = scanner.moveOn(SymDoubleQuestion, ch, next)
} else if next == '=' {
tk = scanner.moveOn(SymQuestionEqual, ch, next)
} else if next == '!' {
tk = scanner.moveOn(SymQuestionExclam, ch, next)
} else {
tk = self.makeToken(SymQuestion, ch)
tk = scanner.makeToken(SymQuestion, ch)
}
case '&':
if next, _ := self.peek(); next == '&' {
tk = self.moveOn(SymDoubleAmpersand, ch, next)
if next, _ := scanner.peek(); next == '&' {
tk = scanner.moveOn(SymDoubleAmpersand, ch, next)
} else {
tk = self.makeToken(SymAmpersand, ch)
tk = scanner.makeToken(SymAmpersand, ch)
}
case '%':
tk = self.makeToken(SymPercent, ch)
tk = scanner.makeToken(SymPercent, ch)
case '#':
tk = self.makeToken(SymHash, ch)
tk = scanner.makeToken(SymHash, ch)
case '@':
if next, _ := self.peek(); (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z') {
self.readChar()
if tk = self.fetchIdentifier(next); tk.Sym == SymIdentifier {
if next, _ := scanner.peek(); (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z') {
scanner.readChar()
if tk = scanner.fetchIdentifier(next); tk.Sym == SymIdentifier {
//tk.Sym = SymIdRef
tk.source = "@" + tk.source
} 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 == '@' {
tk = self.moveOn(SymDoubleAt, ch, next)
tk = scanner.moveOn(SymDoubleAt, ch, next)
} else {
tk = self.makeToken(SymAt, ch)
tk = scanner.makeToken(SymAt, ch)
}
case '_':
tk = self.makeToken(SymUndescore, ch)
tk = scanner.makeToken(SymUndescore, ch)
case '=':
if next, _ := self.peek(); next == '=' {
tk = self.moveOn(SymDoubleEqual, ch, next)
if next, _ := scanner.peek(); next == '=' {
tk = scanner.moveOn(SymDoubleEqual, ch, next)
} else {
tk = self.makeToken(SymEqual, ch)
tk = scanner.makeToken(SymEqual, ch)
}
case '<':
if next, _ := self.peek(); next == '=' {
tk = self.moveOn(SymLessOrEqual, ch, next)
if next, _ := scanner.peek(); next == '=' {
tk = scanner.moveOn(SymLessOrEqual, ch, next)
} else if next == '<' {
tk = self.moveOn(SymAppend, ch, next)
tk = scanner.moveOn(SymAppend, ch, next)
} else if next == '>' {
tk = self.moveOn(SymLessGreater, ch, next)
tk = scanner.moveOn(SymLessGreater, ch, next)
} else {
tk = self.makeToken(SymLess, ch)
tk = scanner.makeToken(SymLess, ch)
}
case '>':
if next, _ := self.peek(); next == '=' {
tk = self.moveOn(SymGreaterOrEqual, ch, next)
if next, _ := scanner.peek(); next == '=' {
tk = scanner.moveOn(SymGreaterOrEqual, ch, next)
} else if next == '>' {
tk = self.moveOn(SymInsert, ch, next)
tk = scanner.moveOn(SymInsert, ch, next)
} else {
tk = self.makeToken(SymGreater, ch)
tk = scanner.makeToken(SymGreater, ch)
}
case '$':
if next, _ := self.peek(); next == '(' {
tk = self.moveOn(SymDollarRound, ch, next)
if next, _ := scanner.peek(); next == '(' {
tk = scanner.moveOn(SymDollarRound, ch, next)
tk.source += ")"
} else if next == '$' {
tk = self.moveOn(SymDoubleDollar, ch, next)
tk = scanner.moveOn(SymDoubleDollar, ch, next)
} else {
tk = self.makeToken(SymDollar, ch)
tk = scanner.makeToken(SymDollar, ch)
}
case '(':
if next, _ := self.peek(); next == ')' {
tk = self.moveOn(SymOpenClosedRound, ch, next)
} else {
tk = self.makeToken(SymOpenRound, ch)
}
// if next, _ := scanner.peek(); next == ')' {
// tk = scanner.moveOn(SymOpenClosedRound, ch, next)
// } else {
tk = scanner.makeToken(SymOpenRound, ch)
// }
case ')':
tk = self.makeToken(SymClosedRound, ch)
tk = scanner.makeToken(SymClosedRound, ch)
case '[':
tk = self.makeToken(SymOpenSquare, ch)
tk = scanner.makeToken(SymOpenSquare, ch)
case ']':
tk = self.makeToken(SymClosedSquare, ch)
tk = scanner.makeToken(SymClosedSquare, ch)
case '{':
tk = self.makeToken(SymOpenBrace, ch)
tk = scanner.makeToken(SymOpenBrace, ch)
case '}':
tk = self.makeToken(SymClosedBrace, ch)
tk = scanner.makeToken(SymClosedBrace, ch)
case '~':
tk = self.makeToken(SymTilde, ch)
tk = scanner.makeToken(SymTilde, ch)
case 0:
if escape {
tk = self.makeErrorToken(errors.New("incomplete escape sequence"))
tk = scanner.makeErrorToken(errors.New("incomplete escape sequence"))
}
escape = false
default:
if /*ch == '_' ||*/ (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') {
if tk = self.fetchIdentifier(ch); tk.Sym == SymKwFunc {
if next, _ := self.peek(); next == '(' {
tk = self.moveOn(SymFuncDef, ch, next)
if tk = scanner.fetchIdentifier(ch); tk.Sym == SymKwFunc {
if next, _ := scanner.peek(); next == '(' {
tk = scanner.moveOn(SymFuncDef, ch, next)
}
}
} else if ch >= '0' && ch <= '9' {
tk = self.parseNumber(ch)
tk = scanner.parseNumber(ch)
}
}
if !escape {
@@ -305,14 +327,14 @@ func (self *scanner) fetchNextToken() (tk *Token) {
}
}
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
}
func (self *scanner) sync(err error) error {
func (scanner *scanner) sync(err error) error {
if err == nil {
err = self.unreadChar()
err = scanner.unreadChar()
}
return err
}
@@ -333,32 +355,32 @@ func isHexDigit(ch byte) bool {
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 digitType string
firstCh = currentFirstCh
digitFunc = isDecimalDigit
numBase = 10
if ch, err = self.peek(); err == nil {
if ch, err = scanner.peek(); err == nil {
if ch == 'b' || ch == 'B' {
numBase = 2
digitType = "binary"
self.readChar()
scanner.readChar()
digitFunc = isBinaryDigit
firstCh, err = self.readChar()
firstCh, err = scanner.readChar()
} else if ch == 'o' || ch == 'O' {
numBase = 8
digitType = "octal"
self.readChar()
scanner.readChar()
digitFunc = isOctalDigit
firstCh, err = self.readChar()
firstCh, err = scanner.readChar()
} else if ch == 'x' || ch == 'X' {
numBase = 16
digitType = "hex"
self.readChar()
scanner.readChar()
digitFunc = isHexDigit
firstCh, err = self.readChar()
firstCh, err = scanner.readChar()
}
if err == nil && !digitFunc(firstCh) {
if len(digitType) == 0 {
@@ -372,7 +394,7 @@ func (self *scanner) initBase(sb *strings.Builder, currentFirstCh byte) (firstCh
return
}
func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
func (scanner *scanner) parseNumber(firstCh byte) (tk *Token) {
var err error
var ch byte
var sym Symbol = SymInteger
@@ -381,9 +403,9 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
var numBase = 10
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)
}
@@ -391,9 +413,9 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
if err == nil && ch == '.' {
sym = SymFloat
sb.WriteByte(ch)
ch, err = self.readChar()
ch, err = scanner.readChar()
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)
}
}
@@ -402,32 +424,32 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
if ch == 'e' || ch == 'E' {
sym = SymFloat
sb.WriteByte(ch)
if ch, err = self.readChar(); err == nil {
if ch, err = scanner.readChar(); err == nil {
if ch == '+' || ch == '-' {
sb.WriteByte(ch)
ch, err = self.readChar()
ch, err = scanner.readChar()
}
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)
}
} 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 == '(' {
sym = SymFraction
sb.WriteByte(ch)
ch, err = self.readChar()
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
ch, err = scanner.readChar()
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = scanner.readChar() {
sb.WriteByte(ch)
}
if err == nil {
if ch != ')' {
err = fmt.Errorf("[%d:%d] expected ')', got '%c'", self.row, self.column, ch)
err = fmt.Errorf("[%d:%d] expected ')', got '%c'", scanner.row, scanner.column, ch)
} else {
sb.WriteByte(ch)
_, err = self.readChar()
_, err = scanner.readChar()
}
}
}
@@ -435,10 +457,10 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
}
if err != nil && err != io.EOF {
tk = self.makeErrorToken(err)
tk = scanner.makeErrorToken(err)
} else {
var value any
err = self.sync(err)
_ = scanner.sync(err) // TODO: Check this function
txt := sb.String()
if sym == SymFloat {
value, err = strconv.ParseFloat(txt, 64)
@@ -448,39 +470,39 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
value, err = strconv.ParseInt(txt, numBase, 64)
}
if err == nil {
tk = self.makeValueToken(sym, txt, value)
tk = scanner.makeValueToken(sym, txt, value)
} else {
tk = self.makeErrorToken(err)
tk = scanner.makeErrorToken(err)
}
}
return
}
func (self *scanner) fetchIdentifier(firstCh byte) (tk *Token) {
func (scanner *scanner) fetchIdentifier(firstCh byte) (tk *Token) {
var err error
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)
}
if err != nil && err != io.EOF {
tk = self.makeErrorToken(err)
} else if err = self.sync(err); err != nil && err != io.EOF {
tk = self.makeErrorToken(err)
tk = scanner.makeErrorToken(err)
} else if err = scanner.sync(err); err != nil && err != io.EOF {
tk = scanner.makeErrorToken(err)
} else {
txt := sb.String()
uptxt := strings.ToUpper(txt)
if sym, ok := keywords[uptxt]; ok {
tk = self.makeKeywordToken(sym, uptxt)
tk = scanner.makeKeywordToken(sym, uptxt)
} else if uptxt == `TRUE` {
tk = self.makeValueToken(SymBool, txt, true)
tk = scanner.makeValueToken(SymBool, txt, true)
} else if uptxt == `FALSE` {
tk = self.makeValueToken(SymBool, txt, false)
} else if ch, _ := self.peek(); ch == '(' {
self.readChar()
tk = self.makeValueToken(SymFuncCall, txt+"(", txt)
tk = scanner.makeValueToken(SymBool, txt, false)
} else if ch, _ := scanner.peek(); ch == '(' {
scanner.readChar()
tk = scanner.makeValueToken(SymFuncCall, txt+"(", txt)
} else {
tk = self.makeValueToken(SymIdentifier, txt, txt)
tk = scanner.makeValueToken(SymIdentifier, txt, txt)
}
}
@@ -500,29 +522,29 @@ func (self *scanner) fetchIdentifier(firstCh byte) (tk *Token) {
return
}
func (self *scanner) fetchBlockComment() *Token {
return self.fetchUntil(SymComment, false, '*', '/')
func (scanner *scanner) fetchBlockComment() *Token {
return scanner.fetchUntil(SymComment, false, '*', '/')
}
func (self *scanner) fetchOnLineComment() *Token {
return self.fetchUntil(SymComment, true, '\n')
func (scanner *scanner) fetchOnLineComment() *Token {
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 ch byte
var sb strings.Builder
var value string
ring := NewByteSlider(len(endings))
endReached := false
for ch, err = self.readChar(); err == nil && !endReached; {
for ch, err = scanner.readChar(); err == nil && !endReached; {
sb.WriteByte(ch)
ring.PushEnd(ch)
if ring.Equal(endings) {
value = sb.String()[0 : sb.Len()-len(endings)]
endReached = true
} else {
ch, err = self.readChar()
ch, err = scanner.readChar()
}
}
if !endReached && allowEos {
@@ -531,18 +553,18 @@ func (self *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk
}
if endReached {
tk = self.makeValueToken(sym, "", value)
tk = scanner.makeValueToken(sym, "", value)
} else {
tk = self.makeErrorToken(err)
tk = scanner.makeErrorToken(err)
}
return
}
func (self *scanner) fetchString(termCh byte) (tk *Token) {
func (scanner *scanner) fetchString(termCh byte) (tk *Token) {
var err error
var ch, prev byte
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 == '\\' {
switch ch {
case '"':
@@ -570,65 +592,65 @@ func (self *scanner) fetchString(termCh byte) (tk *Token) {
}
if err != nil {
if err == io.EOF {
tk = self.makeErrorToken(errors.New("missing string termination \""))
tk = scanner.makeErrorToken(errors.New(string(termCh)))
} else {
tk = self.makeErrorToken(err)
tk = scanner.makeErrorToken(err)
}
} else {
txt := sb.String()
tk = self.makeValueToken(SymString, `"`+txt+`"`, txt)
tk = scanner.makeValueToken(SymString, `"`+txt+`"`, txt)
}
return
}
func (self *scanner) peek() (next byte, err error) {
func (scanner *scanner) peek() (next byte, err error) {
var one []byte
if one, err = self.stream.Peek(1); err == nil {
if one, err = scanner.stream.Peek(1); err == nil {
next = one[0]
}
return
}
func (self *scanner) skipBlanks() (err error) {
func (scanner *scanner) skipBlanks() (err error) {
var one []byte
for one, err = self.stream.Peek(1); err == nil && one[0] <= 32; one, err = self.stream.Peek(1) {
self.readChar()
for one, err = scanner.stream.Peek(1); err == nil && one[0] <= 32; one, err = scanner.stream.Peek(1) {
scanner.readChar()
}
return
}
func (self *scanner) translate(sym Symbol) Symbol {
if self.translations != nil {
if translatedSym, ok := self.translations[sym]; ok {
func (scanner *scanner) translate(sym Symbol) Symbol {
if scanner.translations != nil {
if translatedSym, ok := scanner.translations[sym]; ok {
return translatedSym
}
}
return sym
}
func (self *scanner) moveOn(sym Symbol, chars ...byte) (tk *Token) {
tk = NewToken(self.row, self.column, self.translate(sym), string(chars))
func (scanner *scanner) moveOn(sym Symbol, chars ...byte) (tk *Token) {
tk = NewToken(scanner.row, scanner.column, scanner.translate(sym), string(chars))
for i := 1; i < len(chars); i++ {
self.readChar()
scanner.readChar()
}
return
}
func (self *scanner) makeToken(sym Symbol, chars ...byte) (tk *Token) {
tk = NewToken(self.row, self.column, self.translate(sym), string(chars))
func (scanner *scanner) makeToken(sym Symbol, chars ...byte) (tk *Token) {
tk = NewToken(scanner.row, scanner.column, scanner.translate(sym), string(chars))
return
}
func (self *scanner) makeKeywordToken(sym Symbol, upperCaseKeyword string) (tk *Token) {
tk = NewToken(self.row, self.column, self.translate(sym), upperCaseKeyword)
func (scanner *scanner) makeKeywordToken(sym Symbol, upperCaseKeyword string) (tk *Token) {
tk = NewToken(scanner.row, scanner.column, scanner.translate(sym), upperCaseKeyword)
return
}
func (self *scanner) makeValueToken(sym Symbol, source string, value any) (tk *Token) {
tk = NewValueToken(self.row, self.column, self.translate(sym), source, value)
func (scanner *scanner) makeValueToken(sym Symbol, source string, value any) (tk *Token) {
tk = NewValueToken(scanner.row, scanner.column, scanner.translate(sym), source, value)
return
}
func (self *scanner) makeErrorToken(err error) *Token {
return NewErrorToken(self.row, self.column, err)
func (scanner *scanner) makeErrorToken(err error) *Token {
return NewErrorToken(scanner.row, scanner.column, err)
}
+31 -81
View File
@@ -25,7 +25,7 @@ func NewSimpleStore() *SimpleStore {
}
func filterRefName(name string) bool { return name[0] != '@' }
func filterPrivName(name string) bool { return name[0] != '_' }
//func filterPrivName(name string) bool { return name[0] != '_' }
func (ctx *SimpleStore) SetParent(parentCtx ExprContext) {
ctx.parent = parentCtx
@@ -36,88 +36,21 @@ func (ctx *SimpleStore) GetParent() ExprContext {
}
func (ctx *SimpleStore) Clone() ExprContext {
return &SimpleStore{
clone := &SimpleStore{
varStore: CloneFilteredMap(ctx.varStore, filterRefName),
funcStore: CloneFilteredMap(ctx.funcStore, filterRefName),
}
return clone
}
func (ctx *SimpleStore) Merge(src ExprContext) {
for _, name := range src.EnumVars(filterRefName) {
ctx.varStore[name], _ = src.GetVar(name)
}
for _, name := range src.EnumFuncs(filterRefName) {
ctx.funcStore[name], _ = src.GetFuncInfo(name)
}
}
/*
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("vars: {\n")
first := true
for _, name := range ctx.EnumVars(nil) {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
value, _ := ctx.GetVar(name)
sb.WriteString(strings.Repeat("\t", indent+1))
sb.WriteString(name)
sb.WriteString(": ")
if f, ok := value.(Formatter); ok {
sb.WriteString(f.ToString(0))
} else if _, ok = value.(Functor); ok {
sb.WriteString("func(){}")
// } else if _, ok = value.(map[any]any); ok {
// sb.WriteString("dict{}")
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteString(strings.Repeat("\t", indent))
sb.WriteString("\n}")
}
func varsCtxToString(ctx ExprContext, indent int) string {
var sb strings.Builder
varsCtxToBuilder(&sb, ctx, indent)
return sb.String()
}
func funcsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) {
sb.WriteString("funcs: {\n")
first := true
names := ctx.EnumFuncs(func(name string) bool { return true })
slices.Sort(names)
for _, name := range names {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
value, _ := ctx.GetFuncInfo(name)
sb.WriteString(strings.Repeat("\t", indent+1))
if formatter, ok := value.(Formatter); ok {
sb.WriteString(formatter.ToString(0))
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteString("\n}")
}
func (ctx *SimpleStore) ToString(opt FmtOpt) string {
var sb strings.Builder
varsCtxToBuilder(&sb, ctx, 0)
sb.WriteByte('\n')
funcsCtxToBuilder(&sb, ctx, 0)
return sb.String()
}
*/
// func (ctx *SimpleStore) 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()
@@ -199,6 +132,14 @@ func (ctx *SimpleStore) EnumVars(acceptor func(name string) (accept bool)) (varN
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
@@ -208,10 +149,11 @@ func (ctx *SimpleStore) RegisterFuncInfo(info ExprFunc) {
ctx.funcStore[info.Name()], _ = info.(*funcInfo)
}
func (ctx *SimpleStore) RegisterFunc(name string, functor Functor, returnType string, params []ExprFuncParam) (err error) {
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
}
@@ -230,10 +172,18 @@ func (ctx *SimpleStore) EnumFuncs(acceptor func(name string) (accept bool)) (fun
return
}
func (ctx *SimpleStore) Call(name string, args []any) (result any, err error) {
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.Invoke(ctx, name, args)
result, err = functor.InvokeNamed(ctx, name, args)
} else {
err = fmt.Errorf("unknown function %s()", name)
}
+181
View File
@@ -0,0 +1,181 @@
// 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
symClassPostOp
symClassIdentifier
symClassDelimiter
symClassParenthesis
symClassDeclaration
symClassValue
symClassOther
)
type symbolSpec struct {
repr string
kind symbolClass
}
func init() {
symbolMap = map[Symbol]symbolSpec{
SymUnknown: {"<unknown>", symClassOther}, // -1: Unknown symbol
SymNone: {"<null>", symClassOther}, // 0: Null value for variable of type symbol
SymError: {"<error>", symClassOther}, // 1: Error reading from stream
SymEos: {"<eos>", symClassOther}, // 2: End of stream
SymMinus: {"-", symClassOperator}, // 3: '-'
SymMinusEqual: {"-=", symClassOperator}, // 4: '-='
SymDoubleMinus: {"--", symClassOperator}, // 5: '--'
SymPlus: {"+", symClassOperator}, // 6: '+'
SymPlusEqual: {"+=", symClassOperator}, // 7: '+='
SymDoublePlus: {"++", symClassOperator}, // 8: '++'
SymStar: {"*", symClassOperator}, // 9: '*'
SymDoubleStar: {"**", symClassOperator}, // 10: '**'
SymSlash: {"/", symClassOperator}, // 11: '/'
SymBackSlash: {"\\", symClassOperator}, // 12: '\'
SymVertBar: {"|", symClassOperator}, // 13: '|'
SymDoubleVertBar: {"||", symClassOperator}, // 14: '||'
SymComma: {",", symClassOperator}, // 15: ','
SymColon: {":", symClassOperator}, // 16: ':'
SymSemiColon: {";", symClassOperator}, // 17: ';'
SymDot: {".", symClassOperator}, // 18: '.'
SymDotSlash: {"./", symClassOperator}, // 19: './'
SymQuote: {"'", symClassDelimiter}, // 20: '\''
SymDoubleQuote: {"\"", symClassDelimiter}, // 21: '"'
SymBackTick: {"`", symClassOperator}, // 22: '`'
SymExclamation: {"!", symClassPostOp}, // 23: '!'
SymQuestion: {"?", symClassOperator}, // 24: '?'
SymAmpersand: {"&", symClassOperator}, // 25: '&'
SymDoubleAmpersand: {"&&", symClassOperator}, // 26: '&&'
SymPercent: {"%", symClassOperator}, // 27: '%'
SymAt: {"@", symClassOperator}, // 28: '@'
SymUndescore: {"_", symClassOperator}, // 29: '_'
SymEqual: {"=", symClassOperator}, // 30: '='
SymDoubleEqual: {"==", symClassOperator}, // 31: '=='
SymLess: {"<", symClassOperator}, // 32: '<'
SymLessOrEqual: {"<=", symClassOperator}, // 33: '<='
SymGreater: {">", symClassOperator}, // 34: '>'
SymGreaterOrEqual: {">=", symClassOperator}, // 35: '>='
SymLessGreater: {"<>", symClassOperator}, // 36: '<>'
SymNotEqual: {"!=", symClassOperator}, // 37: '!='
SymDollar: {"$", symClassOperator}, // 38: '$'
SymHash: {"#", symClassOperator}, // 39: '#'
SymOpenRound: {"(", symClassParenthesis}, // 40: '('
SymClosedRound: {")", symClassParenthesis}, // 41: ')'
SymOpenSquare: {"[", symClassParenthesis}, // 42: '['
SymClosedSquare: {"]", symClassParenthesis}, // 43: ']'
SymOpenBrace: {"{", symClassParenthesis}, // 44: '{'
SymClosedBrace: {"}", symClassParenthesis}, // 45: '}'
SymTilde: {"~", symClassOperator}, // 46: '~'
SymDoubleQuestion: {"??", symClassOperator}, // 47: '??'
SymQuestionEqual: {"?=", symClassOperator}, // 48: '?='
SymQuestionExclam: {"?!", symClassOperator}, // 49: '?!'
SymDoubleAt: {"@@", symClassOperator}, // 50: '@@'
SymDoubleColon: {"::", symClassOperator}, // 51: '::'
SymInsert: {">>", symClassOperator}, // 52: '>>'
SymAppend: {"<<", symClassOperator}, // 53: '<<'
SymCaret: {"^", symClassOperator}, // 54: '^'
SymDollarRound: {"$(", symClassOperator}, // 55: '$('
SymOpenClosedRound: {"()", symClassPostOp}, // 56: '()'
SymDoubleDollar: {"$$", symClassOperator}, // 57: '$$'
SymDoubleDot: {"..", symClassOperator}, // 58: '..'
SymTripleDot: {"...", symClassOperator}, // 59: '...'
SymStarEqual: {"*=", symClassOperator}, // 60: '*='
SymSlashEqual: {"/=", symClassOperator}, // 61: '/='
SymPercEqual: {"%=", symClassOperator}, // 62: '%='
// 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},
SymKwNot: {"not", symClassOperator},
SymKwOr: {"or", symClassOperator},
SymKwBut: {"but", symClassOperator},
SymKwFunc: {"func(", symClassDeclaration},
SymKwBuiltin: {"builtin", symClassOperator},
SymKwPlugin: {"plugin", symClassOperator},
SymKwIn: {"in", symClassOperator},
SymKwInclude: {"include", symClassOperator},
SymKwNil: {"nil", symClassValue},
SymKwUnset: {"unset", symClassOperator},
}
}
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) {
sym = SymNone
lower := strings.ToLower(s)
for symbol, spec := range symbolMap {
if spec.kind == symClassOperator && strings.HasSuffix(lower, spec.repr) {
sym = symbol
break
}
}
return
}
+17 -10
View File
@@ -57,16 +57,20 @@ const (
SymTilde // 46: '~'
SymDoubleQuestion // 47: '??'
SymQuestionEqual // 48: '?='
SymDoubleAt // 49: '@@'
SymDoubleColon // 50: '::'
SymInsert // 51: '>>'
SymAppend // 52: '<<'
SymCaret // 53: '^'
SymDollarRound // 54: '$('
SymOpenClosedRound // 55: '()'
SymDoubleDollar // 56: '$$'
SymDoubleDot // 57: '..'
SymTripleDot // 58: '...'
SymQuestionExclam // 49: '?!'
SymDoubleAt // 50: '@@'
SymDoubleColon // 51: '::'
SymInsert // 52: '>>'
SymAppend // 53: '<<'
SymCaret // 54: '^'
SymDollarRound // 55: '$('
SymOpenClosedRound // 56: '()'
SymDoubleDollar // 57: '$$'
SymDoubleDot // 58: '..'
SymTripleDot // 59: '...'
SymStarEqual // 60: '*='
SymSlashEqual // 61: '/='
SymPercEqual // 62: '%='
SymChangeSign
SymUnchangeSign
SymIdentifier
@@ -86,6 +90,7 @@ const (
SymList
SymDict
SymIndex
SymRange // [index : index]
SymExpression
SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
@@ -105,6 +110,7 @@ const (
SymKwIn
SymKwInclude
SymKwNil
SymKwUnset
)
var keywords map[string]Symbol
@@ -122,5 +128,6 @@ func init() {
"NOT": SymKwNot,
"OR": SymKwOr,
"NIL": SymKwNil,
"UNSET": SymKwUnset,
}
}
+1 -1
View File
@@ -47,7 +47,7 @@ func TestAddUnknownTokens(t *testing.T) {
wantErr := errors.New(`unexpected token "%"`)
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)
}
}
+66
View File
@@ -0,0 +1,66 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_bool_test.go
package expr
import (
"errors"
"testing"
)
func TestBool(t *testing.T) {
section := "Bool"
inputs := []inputType{
/* 1 */ {`true`, true, nil},
/* 2 */ {`false`, false, nil},
/* 3 */ {`not false`, true, nil},
/* 4 */ {`not 1`, false, nil},
/* 5 */ {`not "true"`, false, nil},
/* 6 */ {`not "false"`, false, nil},
/* 7 */ {`not ""`, true, nil},
/* 8 */ {`not []`, nil, errors.New(`[1:4] prefix/postfix operator "NOT" do not support operand '[]' [list]`)},
/* 9 */ {`true and false`, false, nil},
/* 10 */ {`true and []`, nil, errors.New(`[1:9] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "AND"`)},
/* 11 */ {`[] and false`, nil, errors.New(`got list as left operand type of 'AND' operator, it must be bool`)},
/* 12 */ {`true or false`, true, nil},
/* 13 */ {`true or []`, true, nil},
/* 14 */ {`[] or false`, nil, errors.New(`got list as left operand type of 'OR' operator, it must be bool`)},
/* 13 */ //{`true or []`, nil, errors.New(`[1:8] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "OR"`)},
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 1)
runTestSuite(t, section, inputs)
}
func TestBoolNoShortcut(t *testing.T) {
section := "Bool-NoShortcut"
inputs := []inputType{
/* 1 */ {`true`, true, nil},
/* 2 */ {`false`, false, nil},
/* 3 */ {`not false`, true, nil},
/* 4 */ {`not 1`, false, nil},
/* 5 */ {`not "true"`, false, nil},
/* 6 */ {`not "false"`, false, nil},
/* 7 */ {`not ""`, true, nil},
/* 8 */ {`not []`, nil, `[1:4] prefix/postfix operator "NOT" do not support operand '[]' [list]`},
/* 9 */ {`true and false`, false, nil},
/* 10 */ {`true and []`, nil, `[1:9] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "AND"`},
/* 11 */ {`[] and false`, nil, `[1:7] left operand '[]' [list] and right operand 'false' [bool] are not compatible with operator "AND"`},
/* 12 */ {`true or false`, true, nil},
/* 13 */ {`true or []`, nil, `[1:8] left operand 'true' [bool] and right operand '[]' [list] are not compatible with operator "OR"`},
/* 14 */ {`[] or false`, nil, `[1:6] left operand '[]' [list] and right operand 'false' [bool] are not compatible with operator "OR"`},
}
// t.Setenv("EXPR_PATH", ".")
ctx := NewSimpleStore()
current := SetCtrl(ctx, ControlBoolShortcut, false)
// runCtxTestSuiteSpec(t, ctx, section, inputs, 1)
runCtxTestSuite(t, ctx, section, inputs)
SetCtrl(ctx, ControlBoolShortcut, current)
}
+18 -18
View File
@@ -5,7 +5,6 @@
package expr
import (
"errors"
"testing"
)
@@ -21,9 +20,9 @@ func TestFuncBase(t *testing.T) {
/* 6 */ {`int(3.1)`, int64(3), nil},
/* 7 */ {`int(3.9)`, int64(3), nil},
/* 8 */ {`int("432")`, int64(432), nil},
/* 9 */ {`int("1.5")`, nil, errors.New(`strconv.Atoi: parsing "1.5": invalid syntax`)},
/* 10 */ {`int("432", 4)`, nil, errors.New(`int(): too much params -- expected 1, got 2`)},
/* 11 */ {`int(nil)`, nil, errors.New(`int(): can't convert nil to int`)},
/* 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},
@@ -31,9 +30,9 @@ func TestFuncBase(t *testing.T) {
/* 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},
/* 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},
@@ -42,26 +41,27 @@ func TestFuncBase(t *testing.T) {
/* 27 */ {`dec(2.0)`, float64(2), nil},
/* 28 */ {`dec("2.0")`, float64(2), nil},
/* 29 */ {`dec(true)`, float64(1), nil},
/* 30 */ {`dec(true")`, nil, errors.New("[1:11] missing string termination \"")},
/* 31 */ {`dec()`, nil, errors.New(`dec(): too few params -- expected 1, got 0`)},
/* 32 */ {`dec(1,2,3)`, nil, errors.New(`dec(): too much params -- expected 1, got 3`)},
/* 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},
/* 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},
/* 37 */ {`bool(1:2)`, true, nil},
/* 38 */ {`bool(1.0)`, true, nil},
/* 39 */ {`bool("1")`, true, nil},
/* 40 */ {`bool(false)`, false, nil},
/* 41 */ {`bool([1])`, nil, errors.New(`bool(): can't convert list to bool`)},
/* 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, errors.New(`dec(): can't convert list to float`)},
// /* 45 */ {`string([1])`, nil, errors.New(`string(): can't convert list to string`)},
/* 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", ".")
// parserTestSpec(t, section, inputs, 2)
parserTest(t, section, inputs)
// runTestSuiteSpec(t, section, inputs, 45)
runTestSuite(t, section, inputs)
}
+48
View 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")
}
}
+2 -2
View File
@@ -19,6 +19,6 @@ func TestFuncImport(t *testing.T) {
t.Setenv("EXPR_PATH", "test-resources")
// parserTestSpec(t, section, inputs, 69)
parserTest(t, section, inputs)
//runTestSuiteSpec(t, section, inputs, 1)
runTestSuite(t, section, inputs)
}
+30
View File
@@ -0,0 +1,30 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_builtin-iterator.go
package expr
import (
"testing"
)
func TestFuncRun(t *testing.T) {
section := "Builtin-Iterator"
inputs := []inputType{
/* 1 */ {`builtin "iterator"; it=$(1,2,3); run(it)`, nil, nil},
/* 2 */ {`builtin "iterator"; run($(1,2,3), func(index,item){item+10})`, nil, nil},
/* 3 */ {`builtin "iterator"; run($(1,2,3), func(index,item){status=status+item; true}, {"status":0})`, int64(6), nil},
/* 4 */ {`builtin ["iterator", "fmt"]; run($(1,2,3), func(index,item){println(item+10)})`, nil, nil},
/* 5 */ {`builtin "iterator"; run(nil)`, nil, `paramter "iterator" must be an iterator, passed <nil> [nil]`},
/* 6 */ {`builtin "iterator"; run($(1,2,3), nil)`, nil, nil},
/* 7 */ {`builtin "iterator"; run($(1,2,3), func(){1}, "prrr")`, nil, `paramter "vars" must be a dictionary, passed prrr [string]`},
/* 8 */ {`builtin "iterator"; run($(1,2,3), operator=nil)`, nil, nil},
/* 9 */ {`builtin "iterator"; run($(1,2,3), operatorx=nil)`, nil, `run(): unknown param "operatorx"`},
}
//t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 1)
runTestSuite(t, section, inputs)
}
+8 -4
View File
@@ -5,7 +5,6 @@
package expr
import (
"errors"
"testing"
)
@@ -14,16 +13,21 @@ func TestFuncMathArith(t *testing.T) {
inputs := []inputType{
/* 1 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil},
/* 2 */ {`builtin "math.arith"; add(1,2,3)`, int64(6), nil},
/* 3 */ {`builtin "math.arith"; mulX(1,2,3)`, nil, errors.New(`unknown function mulX()`)},
/* 3 */ {`builtin "math.arith"; mulX(1,2,3)`, nil, `unknown function mulX()`},
/* 4 */ {`builtin "math.arith"; add(1+4,3+2,5*(3-2))`, int64(15), nil},
/* 5 */ {`builtin "math.arith"; add(add(1+4),3+2,5*(3-2))`, int64(15), nil},
/* 6 */ {`builtin "math.arith"; add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil},
/* 7 */ {`builtin "math.arith"; a=5; b=2; add(a, b*3)`, int64(11), nil},
/* 8 */ {`builtin "math.arith"; var2="abc"; add(1,2) but var2`, "abc", nil},
/* 9 */ {`builtin "math.arith"; add()`, int64(0), nil},
/* 10 */ {`builtin "math.arith"; mul()`, int64(1), nil},
/* 11 */ {`builtin "math.arith"; add([1,2,3])`, int64(6), nil},
/* 12 */ {`builtin "math.arith"; mul([2,2,3])`, int64(12), nil},
/* 13 */ {`builtin "math.arith"; mul(2,2,3)`, int64(12), nil},
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 69)
parserTest(t, section, inputs)
//runTestSuiteSpec(t, section, inputs, 10)
runTestSuite(t, section, inputs)
}
+12 -6
View File
@@ -5,7 +5,6 @@
package expr
import (
"errors"
"testing"
)
@@ -14,16 +13,23 @@ func TestFuncOs(t *testing.T) {
inputs := []inputType{
/* 1 */ {`builtin "os.file"`, int64(1), nil},
/* 2 */ {`builtin "os.file"; handle=fileOpen("/etc/hosts"); fileClose(handle)`, true, nil},
/* 3 */ {`builtin "os.file"; handle=fileOpen("/etc/hostsX")`, nil, errors.New(`open /etc/hostsX: no such file or directory`)},
/* 3 */ {`builtin "os.file"; handle=fileOpen("/etc/hostsX")`, nil, `open /etc/hostsX: no such file or directory`},
/* 4 */ {`builtin "os.file"; handle=fileCreate("/tmp/dummy"); fileClose(handle)`, true, nil},
/* 5 */ {`builtin "os.file"; handle=fileAppend("/tmp/dummy"); fileWriteText(handle, "bye-bye"); fileClose(handle)`, true, nil},
/* 6 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); word=fileReadText(handle, "-"); fileClose(handle);word`, "bye", nil},
/* 7 */ {`builtin "os.file"; word=fileReadText(nil, "-")`, nil, errors.New(`fileReadText(): invalid file handle`)},
/* 7 */ {`builtin "os.file"; fileWriteText(nil, "bye")`, nil, errors.New(`fileWriteText(): invalid file handle`)},
/* 7 */ {`builtin "os.file"; word=fileReadText(nil, "-")`, nil, `fileReadText(): invalid file handle`},
/* 8 */ {`builtin "os.file"; fileWriteText(nil, "bye")`, nil, `fileWriteText(): invalid file handle`},
/* 9 */ {`builtin "os.file"; handle=fileOpen()`, nil, `fileOpen(): too few params -- expected 1, got 0`},
/* 10 */ {`builtin "os.file"; handle=fileOpen(123)`, nil, `fileOpen(): missing or invalid file path`},
/* 11 */ {`builtin "os.file"; handle=fileCreate(123)`, nil, `fileCreate(): missing or invalid file path`},
/* 12 */ {`builtin "os.file"; handle=fileAppend(123)`, nil, `fileAppend(): missing or invalid file path`},
/* 13 */ {`builtin "os.file"; handle=fileClose(123)`, nil, `fileClose(): invalid file handle`},
/* 14 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); c=fileReadTextAll(handle); fileClose(handle); c`, "bye-bye", nil},
/* 15 */ {`builtin "os.file"; c=fileReadTextAll(123)`, nil, `fileReadTextAll(): invalid file handle 123 [int64]`},
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 69)
parserTest(t, section, inputs)
//runTestSuiteSpec(t, section, inputs, 2)
runTestSuite(t, section, inputs)
}
+8 -9
View File
@@ -5,7 +5,6 @@
package expr
import (
"errors"
"testing"
)
@@ -16,8 +15,8 @@ func TestFuncString(t *testing.T) {
/* 1 */ {`builtin "string"; strJoin("-", "one", "two", "three")`, "one-two-three", nil},
/* 2 */ {`builtin "string"; strJoin("-", ["one", "two", "three"])`, "one-two-three", nil},
/* 3 */ {`builtin "string"; ls= ["one", "two", "three"]; strJoin("-", ls)`, "one-two-three", nil},
/* 4 */ {`builtin "string"; ls= ["one", "two", "three"]; strJoin(1, ls)`, nil, errors.New(`strJoin(): the "separator" parameter must be a string, got a integer (1)`)},
/* 5 */ {`builtin "string"; ls= ["one", 2, "three"]; strJoin("-", ls)`, nil, errors.New(`strJoin(): expected string, got integer (2)`)},
/* 4 */ {`builtin "string"; ls= ["one", "two", "three"]; strJoin(1, ls)`, nil, `strJoin(): the "separator" parameter must be a string, got an integer (1)`},
/* 5 */ {`builtin "string"; ls= ["one", 2, "three"]; strJoin("-", ls)`, nil, `strJoin(): expected string, got integer (2)`},
/* 6 */ {`builtin "string"; "<"+strTrim(" bye bye ")+">"`, "<bye bye>", nil},
/* 7 */ {`builtin "string"; strSub("0123456789", 1,2)`, "12", nil},
/* 8 */ {`builtin "string"; strSub("0123456789", -3,2)`, "78", nil},
@@ -25,13 +24,13 @@ func TestFuncString(t *testing.T) {
/* 10 */ {`builtin "string"; strSub("0123456789")`, "0123456789", nil},
/* 11 */ {`builtin "string"; strStartsWith("0123456789", "xyz", "012")`, true, nil},
/* 12 */ {`builtin "string"; strStartsWith("0123456789", "xyz", "0125")`, false, nil},
/* 13 */ {`builtin "string"; strStartsWith("0123456789")`, nil, errors.New(`strStartsWith(): too few params -- expected 2 or more, got 1`)},
/* 13 */ {`builtin "string"; strStartsWith("0123456789")`, nil, `strStartsWith(): too few params -- expected 2 or more, got 1`},
/* 14 */ {`builtin "string"; strEndsWith("0123456789", "xyz", "789")`, true, nil},
/* 15 */ {`builtin "string"; strEndsWith("0123456789", "xyz", "0125")`, false, nil},
/* 16 */ {`builtin "string"; strEndsWith("0123456789")`, nil, errors.New(`strEndsWith(): too few params -- expected 2 or more, got 1`)},
/* 16 */ {`builtin "string"; strEndsWith("0123456789")`, nil, `strEndsWith(): too few params -- expected 2 or more, got 1`},
/* 17 */ {`builtin "string"; strSplit("one-two-three", "-")`, newListA("one", "two", "three"), nil},
/* 18 */ {`builtin "string"; strJoin("-", [1, "two", "three"])`, nil, errors.New(`strJoin(): expected string, got integer (1)`)},
/* 19 */ {`builtin "string"; strJoin()`, nil, errors.New(`strJoin(): too few params -- expected 1 or more, got 0`)},
/* 18 */ {`builtin "string"; strJoin("-", [1, "two", "three"])`, nil, `strJoin(): expected string, got integer (1)`},
/* 19 */ {`builtin "string"; strJoin()`, nil, `strJoin(): too few params -- expected 1 or more, got 0`},
/* 69 */ /*{`builtin "string"; $$global`, `vars: {
}
@@ -64,6 +63,6 @@ func TestFuncString(t *testing.T) {
//t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 19)
parserTest(t, section, inputs)
// runTestSuiteSpec(t, section, inputs, 19)
runTestSuite(t, section, inputs)
}
+115
View File
@@ -0,0 +1,115 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_common_test.go
package expr
import (
"errors"
"reflect"
"strings"
"testing"
)
type inputType struct {
source string
wantResult any
wantErr any
}
func runCtxTestSuiteSpec(t *testing.T, ctx ExprContext, section string, inputs []inputType, spec ...int) {
succeeded := 0
failed := 0
for _, count := range spec {
good := doTest(t, ctx, section, &inputs[count-1], count)
if good {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(spec), succeeded, failed)
}
func runTestSuiteSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
runCtxTestSuiteSpec(t, nil, section, inputs, spec...)
}
func runCtxTestSuite(t *testing.T, ctx ExprContext, section string, inputs []inputType) {
succeeded := 0
failed := 0
for i, input := range inputs {
// fmt.Printf("%3d: %s\n", i+1, input.source)
good := doTest(t, ctx, section, &input, i+1)
if good {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}
func runTestSuite(t *testing.T, section string, inputs []inputType) {
runCtxTestSuite(t, nil, section, inputs)
}
func getWantedError(input *inputType) error {
var wantErr error
var ok bool
if wantErr, ok = input.wantErr.(error); !ok {
if msg, ok := input.wantErr.(string); ok {
wantErr = errors.New(msg)
}
}
return wantErr
}
func doTest(t *testing.T, ctx ExprContext, section string, input *inputType, count int) (good bool) {
var expr Expr
var gotResult any
var gotErr error
wantErr := getWantedError(input)
parser := NewParser()
if ctx == nil {
ctx = NewSimpleStore()
}
logTest(t, count, section, input.source, input.wantResult, wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good = true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
eq := reflect.DeepEqual(gotResult, input.wantResult)
if !eq /*gotResult != input.wantResult*/ {
t.Errorf("%d: `%s` -> result = %v [%s], want = %v [%s]", count, input.source, gotResult, TypeName(gotResult), input.wantResult, TypeName(input.wantResult))
good = false
}
if gotErr != wantErr {
if wantErr == nil || gotErr == nil || (gotErr.Error() != wantErr.Error()) {
t.Errorf("%d: %s -> got-err = <%v>, expected-err = <%v>", count, input.source, gotErr, wantErr)
good = false
}
}
return
}
func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
if wantErr == nil {
t.Logf("[+]%s nr %3d -- `%s` --> %v", section, n, source, wantResult)
} else {
t.Logf("[-]%s nr %3d -- `%s` --> %v", section, n, source, wantErr)
}
}
+6 -1
View File
@@ -22,7 +22,7 @@ func TestDictParser(t *testing.T) {
inputs := []inputType{
/* 1 */ {`{}`, map[any]any{}, nil},
/* 2 */ {`{123}`, nil, errors.New(`[1:6] expected ":", got "}"`)},
/* 2 */ {`{123}`, nil, errors.New("[1:6] expected `:`, got `}`")},
/* 3 */ {`{1:"one",2:"two",3:"three"}`, map[int64]any{int64(1): "one", int64(2): "two", int64(3): "three"}, nil},
/* 4 */ {`{1:"one",2:"two",3:"three"}[3]`, "three", nil},
/* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil},
@@ -32,6 +32,11 @@ func TestDictParser(t *testing.T) {
/* 9 */ {`D={"a":1, "b":2}; D["z"]=9; D`, map[any]any{"z": 9, "a": 1, "b": 2}, nil},
/* 10 */ {`D={"a":1, "b":2}; D[nil]=9`, nil, errors.New(`[1:21] index/key is nil`)},
/* 11 */ {`D={"a":1, "b":2}; D["a"]`, int64(1), nil},
/* 12 */ {`m={
"a":1,
//"b":2,
"c":3
}`, map[any]any{"a": 1, "c": 3}, nil},
}
succeeded := 0
+17 -5
View File
@@ -17,10 +17,22 @@ func TestExpr(t *testing.T) {
/* 3 */ {`builtin "os.file"; f=fileOpen("test-file.txt"); line=fileReadText(f); fileClose(f); line`, "uno", nil},
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
/* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil},
/* 6 */ {`
/* 6 */ {`a=3; a+=1; a`, int64(4), nil},
/* 7 */ {`a=3; a-=1; a`, int64(2), nil},
/* 8 */ {`a=3; a*=2; a`, int64(6), nil},
/* 9 */ {`v=[10,20,30]; v[0]+=1; v[0]`, int64(11), nil},
/* 10 */ {`r={"a":10}; r["a"]+=1; r["a"]`, int64(11), nil},
/* 11 */ {`a=3; a/=2`, int64(1), nil},
/* 12 */ {`*=2`, nil, `[1:2] infix operator "*=" requires two non-nil operands, got 1`},
/* 13 */ {`a=3; a*=2+1; a`, int64(9), nil},
/* 14 */ {`a=3; (a*=2)+1; a`, int64(6), nil},
/* 15 */ {`a=3; a*=2)+1; a`, nil, `[1:11] unexpected token ")"`},
/* 16 */ {`v=[2]; a=1; v[a-=1]=5; v[0]`, int64(5), nil},
/* 17 */ {`true ? {"a"} :: {"b"}`, "a", nil},
/* 18 */ {`
ds={
"init":func(end){@end=end; @current=0 but true},
"current":func(){current},
"init":func(@end){@current=0 but true},
//"current":func(){current},
"next":func(){
((next=current+1) <= end) ? [true] {@current=next but current} :: {nil}
}
@@ -32,6 +44,6 @@ func TestExpr(t *testing.T) {
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 3)
parserTest(t, section, inputs)
// runTestSuiteSpec(t, section, inputs, 6, 7, 8, 9)
runTestSuite(t, section, inputs)
}
+26 -23
View File
@@ -5,41 +5,45 @@
package expr
import (
"errors"
"testing"
)
func TestFractionsParser(t *testing.T) {
section := "Fraction"
inputs := []inputType{
/* 1 */ {`1|2`, newFraction(1, 2), nil},
/* 2 */ {`1|2 + 1`, newFraction(3, 2), nil},
/* 3 */ {`1|2 - 1`, newFraction(-1, 2), nil},
/* 4 */ {`1|2 * 1`, newFraction(1, 2), nil},
/* 5 */ {`1|2 * 2|3`, newFraction(2, 6), nil},
/* 6 */ {`1|2 / 2|3`, newFraction(3, 4), nil},
/* 7 */ {`1|"5"`, nil, errors.New(`denominator must be integer, got string (5)`)},
/* 8 */ {`"1"|5`, nil, errors.New(`numerator must be integer, got string (1)`)},
/* 9 */ {`1|+5`, nil, errors.New(`[1:3] infix operator "|" requires two non-nil operands, got 1`)},
/* 10 */ {`1|(-2)`, newFraction(-1, 2), nil},
/* 11 */ {`builtin "math.arith"; add(1|2, 2|3)`, newFraction(7, 6), nil},
/* 12 */ {`builtin "math.arith"; add(1|2, 1.0, 2)`, float64(3.5), nil},
/* 13 */ {`builtin "math.arith"; mul(1|2, 2|3)`, newFraction(2, 6), nil},
/* 14 */ {`builtin "math.arith"; mul(1|2, 1.0, 2)`, float64(1.0), nil},
/* 15 */ {`1|0`, nil, errors.New(`division by zero`)},
/* 1 */ {`1:2`, newFraction(1, 2), nil},
/* 2 */ {`1:2 + 1`, newFraction(3, 2), nil},
/* 3 */ {`1:2 - 1`, newFraction(-1, 2), nil},
/* 4 */ {`1:2 * 1`, newFraction(1, 2), nil},
/* 5 */ {`1:2 * 2:3`, newFraction(2, 6), nil},
/* 6 */ {`1:2 / 2:3`, newFraction(3, 4), nil},
/* 7 */ {`1:"5"`, nil, `denominator must be integer, got string (5)`},
/* 8 */ {`"1":5`, nil, `numerator must be integer, got string (1)`},
/* 9 */ {`1:+5`, newFraction(1, 5), nil},
/* 10 */ {`1:(-2)`, newFraction(-1, 2), nil},
/* 11 */ {`builtin "math.arith"; add(1:2, 2:3)`, newFraction(7, 6), nil},
/* 12 */ {`builtin "math.arith"; add(1:2, 1.0, 2)`, float64(3.5), nil},
/* 13 */ {`builtin "math.arith"; mul(1:2, 2:3)`, newFraction(2, 6), nil},
/* 14 */ {`builtin "math.arith"; mul(1:2, 1.0, 2)`, float64(1.0), nil},
/* 15 */ {`1:0`, nil, `division by zero`},
/* 16 */ {`fract(-0.5)`, newFraction(-1, 2), nil},
/* 17 */ {`fract("")`, (*FractionType)(nil), errors.New(`bad syntax`)},
/* 17 */ {`fract("")`, (*FractionType)(nil), `bad syntax`},
/* 18 */ {`fract("-1")`, newFraction(-1, 1), nil},
/* 19 */ {`fract("+1")`, newFraction(1, 1), nil},
/* 20 */ {`fract("1a")`, (*FractionType)(nil), errors.New(`strconv.ParseInt: parsing "1a": invalid syntax`)},
/* 21 */ {`fract(1,0)`, nil, errors.New(`fract(): division by zero`)},
/* 20 */ {`fract("1a")`, (*FractionType)(nil), `strconv.ParseInt: parsing "1a": invalid syntax`},
/* 21 */ {`fract(1,0)`, nil, `fract(): division by zero`},
/* 22 */ {`string(1:2)`, "1:2", nil},
/* 23 */ {`1+1:2+0.5`, float64(2), nil},
/* 24 */ {`1:(2-2)`, nil, `division by zero`},
/* 25 */ {`[0,1][1-1]:1`, newFraction(0, 1), nil},
}
parserTest(t, section, inputs)
// runTestSuiteSpec(t, section, inputs, 25)
runTestSuite(t, section, inputs)
}
func TestFractionToStringSimple(t *testing.T) {
source := newFraction(1, 2)
want := "1|2"
want := "1:2"
got := source.ToString(0)
if got != want {
t.Errorf(`(1,2) -> result = %v [%T], want = %v [%T]`, got, got, want, want)
@@ -55,8 +59,7 @@ func TestFractionToStringMultiline(t *testing.T) {
}
}
// TODO Check this test: the output string ends with a space
func _TestToStringMultilineTty(t *testing.T) {
func TestToStringMultilineTty(t *testing.T) {
source := newFraction(-1, 2)
want := "\x1b[4m-1\x1b[0m\n 2"
got := source.ToString(MultiLine | TTY)
+18 -9
View File
@@ -5,7 +5,6 @@
package expr
import (
"errors"
"testing"
)
@@ -17,28 +16,38 @@ func TestFuncs(t *testing.T) {
/* 3 */ {`double=func(x){2*x}; double(3)`, int64(6), nil},
/* 4 */ {`double=func(x){2*x}; a=5; double(3+a) + 1`, int64(17), nil},
/* 5 */ {`double=func(x){2*x}; a=5; two=func() {2}; (double(3+a) + 1) * two()`, int64(34), nil},
/* 6 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
/* 6 */ {`@x="hello"; @x`, nil, `[1:3] variable references are not allowed in top level expressions: "@x"`},
/* 7 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
/* 8 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
/* 9 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable or function "x"`)},
/* 9 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, `undefined variable or function "x"`},
/* 10 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 11 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 12 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
/* 13 */ {`two=func(){2}; four=func(f){f()+f()}; four(two)`, int64(4), nil},
/* 14 */ {`two=func(){2}; two(123)`, nil, errors.New(`two(): too much params -- expected 0, got 1`)},
/* 14 */ {`two=func(){2}; two(123)`, nil, `two(): too many params -- expected 0, got 1`},
/* 15 */ {`f=func(x,n=2){x+n}; f(3)`, int64(5), nil},
/* 16 */ {`f=func(x,n=2,y){x+n}`, nil, errors.New(`[1:16] can't mix default and non-default parameters`)},
/* 17 */ {`f=func(x,n){1}; f(3,4,)`, nil, errors.New(`[1:24] expected "function-param-value", got ")"`)},
/* 16 */ {`f=func(x,n=2,y){x+n}`, nil, `[1:16] can't mix default and non-default parameters`},
/* 17 */ {`f=func(x,n){1}; f(3,4,)`, nil, "[1:24] expected `function-param-value`, got `)`"},
/* 18 */ {`factory=func(base){func(){@base=base+1}}; inc10=factory(10); inc5=factory(5); inc10(); inc5(); inc10()`, int64(12), nil},
/* 19 */ {`f=func(a,y=1,z="sos"){}; string(f)`, `f(a, y=1, z="sos"):any{}`, nil},
/* 20 */ {`f=func(a,b){a*2+b}; f(1,10)`, int64(12), nil},
/* 21 */ {`f=func(a,b){a*2+b}; f(a=2,b=1)`, int64(5), nil},
/* 22 */ {`f=func(a,b){a*2+b}; f(b=2,a=1)`, int64(4), nil},
/* 23 */ {`f=func(a=10,b=10){a*2+b}; f(b=1)`, int64(21), nil},
/* 24 */ {`f=func(a,b){a*2+b}; f(a=1,2)`, nil, `f(): positional param nr 2 not allowed after named params`},
/* 25 */ {`f=func(x,x){x}`, nil, `[1:11] parameter "x" at position 2 already defined at position 1`},
// /* 20 */ {`a=[func(){3}]; a[0]()`, int64(3), nil},
// /* 20 */ {`m={}; m["f"]=func(){3}; m["f"]()`, int64(3), nil},
// /* 18 */ {`f=func(a){a*2}`, nil, errors.New(`[1:24] expected "function-param-value", got ")"`)},
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 17)
parserTest(t, section, inputs)
//runTestSuiteSpec(t, section, inputs, 19)
runTestSuite(t, section, inputs)
}
func dummy(ctx ExprContext, name string, args []any) (result any, err error) {
func dummy(ctx ExprContext, name string, args map[string]any) (result any, err error) {
return
}
+40 -39
View File
@@ -5,50 +5,51 @@
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
}
// TODO The new function param model does not allow this kind of test
// ------------------------------------------------------------------
// func subtract(ctx ExprContext, name string, args map[string]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) {
// 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)},
}
// source := `a + b * subtract(4,2)`
// args := []Arg{
// {"a", uint8(1)},
// {"b", int8(2)},
// {"subtract", FuncTemplate2(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)
}
}
// 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) {
@@ -68,7 +69,7 @@ func TestEvalString(t *testing.T) {
// force coverage
ctx.GetFuncInfo("dummy")
ctx.Call("dummy", []any{})
ctx.Call("dummy", map[string]any{})
source := `a + b * f`
+5 -4
View File
@@ -5,7 +5,6 @@
package expr
import (
"errors"
"testing"
)
@@ -17,11 +16,13 @@ func TestCollections(t *testing.T) {
/* 3 */ {`"abcdef"[1:]`, "bcdef", nil},
/* 4 */ {`"abcdef"[:]`, "abcdef", nil},
// /* 5 */ {`[0,1,2,3,4][:]`, ListType{int64(0), int64(1), int64(2), int64(3), int64(4)}, nil},
/* 5 */ {`"abcdef"[1:2:3]`, nil, errors.New(`[1:14] left operand '(1, 2)' [pair] and right operand '3' [integer] are not compatible with operator ":"`)},
/* 5 */ {`"abcdef"[1:2:3]`, nil, `[1:14] invalid range specification`},
/* 6 */ {`"abcdef"[((1>0)?{1}:{0}):3]`, "bc", nil},
/* 7 */ {`"abcdef"[[0,1][0]:1]`, "a", nil},
}
t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 5)
parserTest(t, section, inputs)
// runTestSuiteSpec(t, section, inputs, 5)
runTestSuite(t, section, inputs)
}
+7 -7
View File
@@ -15,7 +15,7 @@ func TestNewListIterator(t *testing.T) {
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "b" {
t.Errorf("expcted %q, got %q", "b", item)
t.Errorf("expected %q, got %q", "b", item)
} else {
t.Logf("Next: %v", item)
}
@@ -27,7 +27,7 @@ func TestNewListIterator2(t *testing.T) {
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "d" {
t.Errorf("expcted %q, got %q", "d", item)
t.Errorf("expected %q, got %q", "d", item)
} else {
t.Logf("Next: %v", item)
}
@@ -39,7 +39,7 @@ func TestNewListIterator3(t *testing.T) {
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "b" {
t.Errorf("expcted %q, got %q", "b", item)
t.Errorf("expected %q, got %q", "b", item)
} else {
t.Logf("Next: %v", item)
}
@@ -51,7 +51,7 @@ func TestNewIterList2(t *testing.T) {
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "a" {
t.Errorf("expcted %q, got %q", "a", item)
t.Errorf("expected %q, got %q", "a", item)
} else {
t.Logf("Next: %v", item)
}
@@ -63,7 +63,7 @@ func TestNewIterList3(t *testing.T) {
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "a" {
t.Errorf("expcted %q, got %q", "a", item)
t.Errorf("expected %q, got %q", "a", item)
} else {
t.Logf("Next: %v", item)
}
@@ -83,7 +83,7 @@ func TestNewIterList5(t *testing.T) {
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "123" {
t.Errorf("expcted %q, got %q", "123", item)
t.Errorf("expected %q, got %q", "123", item)
} else {
t.Logf("Next: %v", item)
}
@@ -96,7 +96,7 @@ func TestNewIterList6(t *testing.T) {
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "a" {
t.Errorf("expcted %q, got %q", "a", item)
t.Errorf("expected %q, got %q", "a", item)
} else {
t.Logf("Next: %v", item)
}
+17 -9
View File
@@ -7,20 +7,28 @@ package expr
import "testing"
func TestIteratorParser(t *testing.T) {
section := "Iterator"
inputs := []inputType{
/* 1 */ {`include "test-resources/iterator.expr"; it=$(ds,3); ()it`, int64(0), nil},
/* 1 */ {`include "test-resources/iterator.expr"; it=$(ds,3); ^it`, int64(0), nil},
/* 2 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++`, int64(1), nil},
/* 3 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; #it`, int64(2), nil},
/* 4 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; it.reset; ()it`, int64(0), nil},
/* 3 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; #it`, int64(3), nil},
/* 4 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; it.reset; ^it`, int64(0), nil},
/* 5 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); add(it)`, int64(6), nil},
/* 6 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); mul(it)`, int64(0), nil},
/* 7 */ {`builtin "math.arith"; include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); mul(it)`, int64(12000), nil},
/* 8 */ {`include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); it++; it.index`, int64(0), nil},
/* 10 */ {`include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); it.clean`, true, nil},
/* 11 */ {`it=$(1,2,3); it++`, int64(1), nil},
/* 9 */ {`include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); it.clean`, nil, nil},
/* 10 */ {`it=$(1,2,3); it++`, int64(1), nil},
/* 11 */ {`it=$(1,2,3); it++; it.reset; it++`, int64(1), nil},
/* 12 */ {`it=$([1,2,3,4],1); it++`, int64(2), nil},
/* 13 */ {`it=$([1,2,3,4],1,3); it++; it++;`, int64(3), nil},
/* 14 */ {`it=$([1,2,3,4],1,3,2); it++; it++;`, int64(4), nil},
/* 15 */ {`it=$([1,2,3,4],1,2,2); it++; it++;`, nil, `EOF`},
/* 16 */ {`include "test-resources/filter.expr"; it=$(ds,10); it++`, int64(2), nil},
/* 17 */ {`it=$({"next":func(){5}}); it++`, int64(5), nil},
/* 18 */ {`it=$({"next":func(){5}}); it.clean`, nil, nil},
}
// inputs1 := []inputType{
// /* 1 */ {`0?{}`, nil, nil},
// }
parserTest(t, "Iterator", inputs)
//runTestSuiteSpec(t, section, inputs, 18)
runTestSuite(t, section, inputs)
}
+12 -13
View File
@@ -5,7 +5,6 @@
package expr
import (
"errors"
"testing"
)
@@ -22,14 +21,14 @@ func TestListParser(t *testing.T) {
/* 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`)},
/* 10 */ {`add([1,"hello"])`, nil, `add(): param nr 2 (2 in 1) has wrong type string, number expected`},
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
/* 12 */ {`[1,2,3] << 2+2`, newListA(int64(1), int64(2), int64(3), int64(4)), nil},
/* 13 */ {`2-1 >> [2,3]`, newListA(int64(1), int64(2), int64(3)), nil},
/* 14 */ {`[1,2,3][1]`, int64(2), nil},
/* 15 */ {`ls=[1,2,3] but ls[1]`, int64(2), nil},
/* 16 */ {`ls=[1,2,3] but ls[-1]`, int64(3), nil},
/* 17 */ {`list=["one","two","three"]; list[10]`, nil, errors.New(`[1:34] index 10 out of bounds`)},
/* 17 */ {`list=["one","two","three"]; list[10]`, nil, `[1:34] index 10 out of bounds`},
/* 18 */ {`["a", "b", "c"]`, newListA("a", "b", "c"), nil},
/* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil},
/* 20 */ {`#["a", "b", "c"]`, int64(3), nil},
@@ -37,18 +36,18 @@ func TestListParser(t *testing.T) {
/* 22 */ {`a=[1,2]; (a)<<3`, newListA(int64(1), int64(2), int64(3)), nil},
/* 23 */ {`a=[1,2]; (a)<<3; a`, newListA(int64(1), int64(2)), nil},
/* 24 */ {`["a","b","c","d"][1]`, "b", nil},
/* 25 */ {`["a","b","c","d"][1,1]`, nil, errors.New(`[1:19] one index only is allowed`)},
/* 25 */ {`["a","b","c","d"][1,1]`, nil, `[1:19] one index only is allowed`},
/* 26 */ {`[0,1,2,3,4][:]`, newListA(int64(0), int64(1), int64(2), int64(3), int64(4)), nil},
/* 27 */ {`["a", "b", "c"] << ;`, nil, errors.New(`[1:18] infix operator "<<" requires two non-nil operands, got 1`)},
/* 28 */ {`2 << 3;`, nil, errors.New(`[1:4] left operand '2' [integer] and right operand '3' [integer] are not compatible with operator "<<"`)},
/* 29 */ {`but >> ["a", "b", "c"]`, nil, errors.New(`[1:6] infix operator ">>" requires two non-nil operands, got 0`)},
/* 30 */ {`2 >> 3;`, nil, errors.New(`[1:4] left operand '2' [integer] and right operand '3' [integer] are not compatible with operator ">>"`)},
/* 27 */ {`["a", "b", "c"] << ;`, nil, `[1:18] infix operator "<<" requires two non-nil operands, got 1`},
/* 28 */ {`2 << 3;`, int64(16), nil},
/* 29 */ {`but >> ["a", "b", "c"]`, nil, `[1:6] infix operator ">>" requires two non-nil operands, got 0`},
/* 30 */ {`2 >> 3;`, int64(0), nil},
/* 31 */ {`a=[1,2]; a<<3`, newListA(int64(1), int64(2), int64(3)), nil},
/* 33 */ {`a=[1,2]; 5>>a`, newListA(int64(5), int64(1), int64(2)), nil},
/* 34 */ {`L=[1,2]; L[0]=9; L`, newListA(int64(9), int64(2)), nil},
/* 35 */ {`L=[1,2]; L[5]=9; L`, nil, errors.New(`index 5 out of bounds (0, 1)`)},
/* 36 */ {`L=[1,2]; L[]=9; L`, nil, errors.New(`[1:12] index/key specification expected, got [] [list]`)},
/* 37 */ {`L=[1,2]; L[nil]=9;`, nil, errors.New(`[1:12] index/key is nil`)},
/* 35 */ {`L=[1,2]; L[5]=9; L`, nil, `index 5 out of bounds (0, 1)`},
/* 36 */ {`L=[1,2]; L[]=9; L`, nil, `[1:12] index/key specification expected, got [] [list]`},
/* 37 */ {`L=[1,2]; L[nil]=9;`, nil, `[1:12] index/key is nil`},
/* 38 */ {`[0,1,2,3,4][2:3]`, newListA(int64(2)), nil},
/* 39 */ {`[0,1,2,3,4][3:-1]`, newListA(int64(3)), nil},
/* 40 */ {`[0,1,2,3,4][-3:-1]`, newListA(int64(2), int64(3)), nil},
@@ -57,6 +56,6 @@ func TestListParser(t *testing.T) {
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 17)
parserTest(t, section, inputs)
// runTestSuiteSpec(t, section, inputs, 1)
runTestSuite(t, section, inputs)
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_operator_test.go
package expr
import (
"testing"
)
func TestOperator(t *testing.T) {
section := "Operator"
inputs := []inputType{
/* 1 */ {`a=1; unset "a"; a`, nil, `undefined variable or function "a"`},
/* 2 */ {`a=1; unset ["a", "b"]`, int64(1), nil},
/* 3 */ {`f=func(){3}; unset "f()"`, int64(1), nil},
}
// t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 3)
runTestSuite(t, section, inputs)
}
+41 -119
View File
@@ -5,18 +5,9 @@
package expr
import (
"errors"
"reflect"
"strings"
"testing"
)
type inputType struct {
source string
wantResult any
wantErr error
}
func TestGeneralParser(t *testing.T) {
section := "Parser"
@@ -57,13 +48,13 @@ func TestGeneralParser(t *testing.T) {
/* 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]`)},
/* 37 */ {`"s" + true`, nil, `[1:6] left operand 's' [string] and right operand 'true' [bool] are not compatible with operator "+"`},
/* 38 */ {`+false`, nil, `[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`)},
/* 40 */ {`1 + // Missing right operator`, nil, `[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`)},
/* 43 */ {"(-4)!", nil, `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},
@@ -75,20 +66,20 @@ func TestGeneralParser(t *testing.T) {
/* 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`)},
/* 55 */ {"1.5 < ", nil, `[1:6] infix operator "<" requires two non-nil operands, got 1`},
/* 56 */ {"1.5 > ", nil, `[1:6] infix operator ">" requires two non-nil operands, got 1`},
/* 57 */ {"1.5 <= ", nil, `[1:6] infix operator "<=" requires two non-nil operands, got 1`},
/* 58 */ {"1.5 >= ", nil, `[1:6] infix operator ">=" requires two non-nil operands, got 1`},
/* 59 */ {"1.5 != ", nil, `[1:6] infix operator "!=" requires two non-nil operands, got 1`},
/* 60 */ {"1.5 == ", nil, `[1:6] infix operator "==" requires two non-nil operands, got 1`},
/* 61 */ {`"1.5" < `, nil, `[1:8] infix operator "<" requires two non-nil operands, got 1`},
/* 62 */ {`"1.5" > `, nil, `[1:8] infix operator ">" requires two non-nil operands, got 1`},
/* 63 */ {`"1.5" == `, nil, `[1:8] infix operator "==" requires two non-nil operands, got 1`},
/* 64 */ {`"1.5" != `, nil, `[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`)},
/* 66 */ {"+", nil, `[1:2] prefix operator "+" requires one non-nil operand`},
/* 67 */ {"4 / 0", nil, `division by zero`},
/* 68 */ {"4.0 / 0", nil, `division by zero`},
/* 69 */ {"4.0 / \n2", float64(2.0), nil},
/* 70 */ {`123`, int64(123), nil},
/* 71 */ {`1.`, float64(1.0), nil},
@@ -100,25 +91,25 @@ func TestGeneralParser(t *testing.T) {
/* 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' [integer] and right operand '2' [float] are not compatible with operator "%"`)},
/* 80 */ {`5 % 2.0`, nil, `[1:4] left operand '5' [integer] and right operand '2' [float] 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},
/* 83 */ {`"a" < "b" AND NOT 2 == 1`, true, nil},
/* 84 */ {`NOT 2 > 1`, false, nil},
/* 85 */ {`nOT true && true`, false, nil},
/* 86 */ {`NOT 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"`)},
/* 93 */ {`false and (x=2 but x==2) or x==2`, nil, `undefined variable or function "x"`},
/* 94 */ {`false or true`, true, nil},
/* 95 */ {`false or (x==2)`, nil, errors.New(`undefined variable or function "x"`)},
/* 95 */ {`false or (x==2)`, nil, `undefined variable or function "x"`},
/* 96 */ {`a=5; a`, int64(5), nil},
/* 97 */ {`2=5`, nil, errors.New(`[1:2] left operand of "=" must be a variable or a collection's item`)},
/* 98 */ {`2+a=5`, nil, errors.New(`[1:3] left operand of "=" must be a variable or a collection's item`)},
/* 97 */ {`2=5`, nil, `[1:2] left operand of "=" must be a variable or a collection's item`},
/* 98 */ {`2+a=5`, nil, `[1:3] left operand of "=" must be a variable or a collection's item`},
/* 99 */ {`2+(a=5)`, int64(7), nil},
/* 100 */ {`x ?? "default"`, "default", nil},
/* 101 */ {`x="hello"; x ?? "default"`, "hello", nil},
@@ -128,98 +119,29 @@ func TestGeneralParser(t *testing.T) {
/* 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`)},
/* 108 */ {`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}`, nil, `[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`)},
/* 111 */ {`10 ? {"a"} : {"b"}`, nil, `[1:3] no case catches the value (10) of the selection expression`},
/* 112 */ {`10 ? {"a"} :: {"b"} : {"c"}`, nil, `[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 "}"`)},
/* 116 */ {`null`, nil, `undefined variable or function "null"`},
/* 117 */ {`{"key"}`, nil, "[1:8] expected `:`, got `}`"},
/* 118 */ {`{"key":}`, nil, "[1:9] expected `dictionary-value`, got `}`"},
/* 119 */ {`{}`, &DictType{}, nil},
/* 120 */ {`v=10; v++; v`, int64(11), nil},
/* 121 */ {`1+1|2+0.5`, float64(2), nil},
/* 122 */ {`1.2()`, newFraction(6, 5), nil},
/* 123 */ {`1|(2-2)`, nil, errors.New(`division by zero`)},
/* 121 */ {`1.2()`, newFraction(6, 5), nil},
/* 122 */ {`x="abc"; x ?! #x`, int64(3), nil},
/* 123 */ {`x ?! #x`, nil, `[1:7] prefix/postfix operator "#" do not support operand '<nil>' [nil]`},
/* 124 */ {`x ?! (x+1)`, nil, nil},
/* 125 */ {`"abx" ?! (x+1)`, nil, `[1:6] left operand of "?!" must be a variable`},
/* 126 */ {`"abx" ?? "pqr"`, nil, `[1:6] left operand of "??" must be a variable`},
/* 127 */ {`"abx" ?= "pqr"`, nil, `[1:6] left operand of "?=" must be a variable`},
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 102)
parserTest(t, section, inputs)
}
func parserTestSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
succeeded := 0
failed := 0
for _, count := range spec {
good := doTest(t, section, &inputs[count-1], count)
if good {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(spec), succeeded, failed)
}
func parserTest(t *testing.T, section string, inputs []inputType) {
succeeded := 0
failed := 0
for i, input := range inputs {
good := doTest(t, section, &input, i+1)
if good {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}
func doTest(t *testing.T, section string, input *inputType, count int) (good bool) {
var expr Expr
var gotResult any
var gotErr error
ctx := NewSimpleStore()
parser := NewParser()
logTest(t, count, section, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good = true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
eq := reflect.DeepEqual(gotResult, input.wantResult)
if !eq /*gotResult != input.wantResult*/ {
t.Errorf("%d: %q -> result = %v [%s], want = %v [%s]", count, input.source, gotResult, TypeName(gotResult), input.wantResult, TypeName(input.wantResult))
good = false
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> got-err = <%v>, expected-err = <%v>", count, input.source, gotErr, input.wantErr)
good = false
}
}
return
}
func logTest(t *testing.T, n int, section, source string, wantResult any, wantErr error) {
if wantErr == nil {
t.Logf("[+]%s nr %3d -- %q --> %v", section, n, source, wantResult)
} else {
t.Logf("[-]%s nr %3d -- %q --> %v", section, n, source, wantErr)
}
//runTestSuiteSpec(t, section, inputs, 130)
runTestSuite(t, section, inputs)
}
+38
View File
@@ -0,0 +1,38 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_plugin_test.go
package expr
import (
"testing"
)
// func TestImportPlugin(t *testing.T) {
// t.Setenv("PLUGINS", "${HOME}/go/src/git.portale-stac.it/go")
// t.Setenv("EXPR_PLUGIN_PATH","${PLUGINS}/expr-json-plugin:${PLUGINS}/expr-csv-plugin")
// gotCount, gotErr := importPluginFromSearchPath("json")
// if gotCount != 1 {
// t.Errorf("Import count: got=%d, want=1", gotCount)
// }
// if gotErr != nil {
// t.Errorf("importPlugin() failed: %v", gotErr)
// }
// }
func TestPluginExists(t *testing.T) {
name := "json"
exists := pluginExists(name)
t.Logf("pluginExists(%v): %v", name, exists)
}
func TestMakePluginName(t *testing.T) {
name := "json"
want := "expr-" + name + "-plugin.so"
if got := makePluginName(name); got != want {
t.Errorf("makePluginName(%q) failed: Got: %q, Want: %q", name, got, want)
}
}
+8 -8
View File
@@ -21,9 +21,9 @@ func TestRelational(t *testing.T) {
/* 8 */ {`true != false`, true, nil},
/* 9 */ {`1.0 != 3.0-2`, false, nil},
/* 10 */ {`[1,2] != [2,1]`, true, nil},
/* 11 */ {`1|2 == 1|3`, false, nil},
/* 12 */ {`1|2 != 1|3`, true, nil},
/* 13 */ {`1|2 == 4|8`, true, nil},
/* 11 */ {`1:2 == 1:3`, false, nil},
/* 12 */ {`1:2 != 1:3`, true, nil},
/* 13 */ {`1:2 == 4:8`, true, nil},
/* 14 */ {`1 < 8`, true, nil},
/* 15 */ {`1 <= 8`, true, nil},
/* 16 */ {`"a" < "b"`, true, nil},
@@ -32,10 +32,10 @@ func TestRelational(t *testing.T) {
/* 19 */ {`1.0 <= 8`, true, nil},
/* 20 */ {`1.0 <= 1.0`, true, nil},
/* 21 */ {`1.0 == 1`, true, nil},
/* 22 */ {`1|2 < 1|3`, false, nil},
/* 23 */ {`1|2 <= 1|3`, false, nil},
/* 24 */ {`1|2 > 1|3`, true, nil},
/* 25 */ {`1|2 >= 1|3`, true, nil},
/* 22 */ {`1:2 < 1:3`, false, nil},
/* 23 */ {`1:2 <= 1:3`, false, nil},
/* 24 */ {`1:2 > 1:3`, true, nil},
/* 25 */ {`1:2 >= 1:3`, true, nil},
/* 26 */ {`[1,2,3] > [2]`, true, nil},
/* 27 */ {`[1,2,3] > [9]`, false, nil},
/* 28 */ {`[1,2,3] >= [6]`, false, nil},
@@ -48,5 +48,5 @@ func TestRelational(t *testing.T) {
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 31)
parserTest(t, section, inputs)
runTestSuite(t, section, inputs)
}
+2 -3
View File
@@ -6,7 +6,6 @@ package expr
import (
"errors"
"fmt"
"reflect"
"strings"
"testing"
@@ -69,9 +68,9 @@ func TestScanner(t *testing.T) {
// continue
// }
if input.wantErr == nil {
t.Log(fmt.Sprintf("[+]Test nr %2d -- %q", i+1, input.source))
t.Logf("[+]Test nr %2d -- %q", i+1, input.source)
} else {
t.Log(fmt.Sprintf("[-]Test nr %2d -- %q", i+1, input.source))
t.Logf("[-]Test nr %2d -- %q", i+1, input.source)
}
r := strings.NewReader(input.source)
+1 -1
View File
@@ -17,5 +17,5 @@ func TestStringsParser(t *testing.T) {
/* 5 */ {`"abc"[1]`, `b`, nil},
/* 6 */ {`#"abc"`, int64(3), nil},
}
parserTest(t, "String", inputs)
runTestSuite(t, "String", inputs)
}
+2 -2
View File
@@ -16,6 +16,6 @@ func TestSomething(t *testing.T) {
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 1)
parserTest(t, section, inputs)
// runTestSuiteSpec(t, section, inputs, 1)
runTestSuite(t, section, inputs)
}

Some files were not shown because too many files have changed in this diff Show More