Compare commits

...

140 Commits

Author SHA1 Message Date
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
camoroso 531cb1c249 Doc: concepts 2024-06-19 09:38:02 +02:00
camoroso d1b468f35b t_list_test.go: new test cases added 2024-06-19 09:24:50 +02:00
camoroso ff9cf80c66 operator-index.go: ConstLastIndex is checked 2024-06-19 09:24:19 +02:00
camoroso d63b18fd76 parser.go: SymColon resets the firstToken flag; that is needed to specify range indeces that can define negative limits 2024-06-19 09:22:40 +02:00
camoroso 019470faf1 operator-range.go: Fixed priority bug; when range only has the left limit, right limit is set to ConstLastIndex constant 2024-06-19 09:20:02 +02:00
camoroso 302430d57d common-params.go: added the constant ConstLastIndex 2024-06-19 09:17:46 +02:00
camoroso 62ef0d699d token.go: new function IsOneOf() 2024-06-19 09:16:19 +02:00
camoroso 866de759dd file-reader.expr: simpler implementation 2024-06-17 14:07:39 +02:00
camoroso b1d6b6de44 refactored dict's item access 2024-06-17 14:06:33 +02:00
camoroso 7e357eea62 changed to comply new builtin-os-file.go's function names 2024-06-17 09:54:20 +02:00
camoroso d6b4c79736 operator-index.go: dict item access by generic keys implemented 2024-06-17 09:05:23 +02:00
camoroso d066344af8 builtin-os-file.go: changed read and write function names; added fileReadTextAll 2024-06-17 06:59:15 +02:00
camoroso f41dba069e plugin.go: support Liteide and VScode to debug executable file name 2024-06-17 06:57:47 +02:00
camoroso 703ecf6829 Added utility function GetLast() to context 2024-06-17 06:56:32 +02:00
camoroso ba479a1b99 Function call moved from operand-func.go to function.go 2024-06-17 06:54:50 +02:00
camoroso 24e6a293b0 common-params.go: ParamName added 2024-06-12 11:16:57 +02:00
camoroso 28f464c4dc plugins.go: If the main program's file name ends with '.debug', plugins will be loaded from file with name ending with '.debug' 2024-06-12 11:15:31 +02:00
camoroso 9fb611aa20 Formatter option is now composed of two data: flags (lower 16 bits) and indentation size (higher 16 bits).
DictType and ListType formatter use both indent and flag options.
Simple-store now makes a DictType from its data and its ToString() function returns dict.ToString()
2024-06-11 16:32:01 +02:00
camoroso 56d6d06d15 simple-store.go: newline removed after context last brace in ToString() 2024-06-10 20:58:38 +02:00
camoroso d9f7e5b1ad common-errors.go: exported all error functions.
builtin-string.go: renamed all functions from somthingStr() to strSomething()
2024-06-10 20:37:58 +02:00
camoroso 0f54e01ef3 t_scanner_test.go: Test nr 25 changed because now single-quotes can enclose strings 2024-06-10 20:36:03 +02:00
camoroso 63f5db00b3 all test file on builtin functions have been renamed from t_func-*_test.go to t_builtin-*_test.go 2024-06-10 20:34:11 +02:00
camoroso 9745a5d909 utils.go: toInt() -> ToInt(); toBool() -> ToBool() 2024-06-10 19:03:39 +02:00
camoroso 0bb4c96481 scanner.go: Strings can be enclosed between two single-quotes too 2024-06-10 18:52:13 +02:00
camoroso 1757298eb4 formatter.go: typeName() renamed as TypeName() 2024-06-10 09:37:27 +02:00
camoroso 54041552d4 dict-type.go: added MakeDict() constructor 2024-06-10 09:35:48 +02:00
camoroso 5302907dcf external plugins can now request for dependencies 2024-06-09 17:12:57 +02:00
camoroso eb4b17f078 moved all test expression files in the test-resources forlder 2024-06-09 16:02:07 +02:00
camoroso 33d70d6d1a splitted go and expr function bindings in dedicated source files 2024-06-09 10:41:06 +02:00
camoroso 9df9ad5dd1 func-*.go modules renamed as builtin-*.go.
Also changed and exported some identiefier relatet to the builtin feature
2024-06-09 10:28:51 +02:00
camoroso 34dc828ead exported some identifier 2024-06-09 10:13:37 +02:00
camoroso 29bc2c62a3 first plugin support.
Module organization requires a better structure to decouple definitions and implementations
2024-06-09 07:41:56 +02:00
camoroso 8eb2d77ea3 improved position of some common functions 2024-06-09 07:38:29 +02:00
camoroso 53bcf90d2a operator-builtin.go: some error messages improved 2024-06-09 07:36:12 +02:00
camoroso f347b15146 Control vars are now stored in the globalCtx only.
However, it is still allowed to store control var in a local context in special situation
2024-06-08 05:49:28 +02:00
camoroso 115ce26ce9 IsOptional() function of ExprFuncParam renamed as IdDefault(). A new IsOptional() function created to check if a param is optional without requiring a default value 2024-06-07 09:45:02 +02:00
camoroso 227944b3fb Removed eval() function from ast interface and implementation. Check on preset control variables is now done in initDefaultVars() 2024-06-07 09:41:10 +02:00
camoroso 0d01afcc9f member ctx removed from struct parser because it is unused 2024-06-07 09:03:42 +02:00
camoroso 00c76b41f1 list-type.go: two special constructors, MakeList() and ListFromStrigns(), added 2024-06-07 09:02:14 +02:00
camoroso 08e0979cdd import-utils.go: addPresetDirs() replaced by addSearchDirs() 2024-06-07 09:01:18 +02:00
camoroso f04f5822ec common-type-names.go: added type name list-of 2024-06-07 08:59:04 +02:00
camoroso 80d47879e9 t_list_test.go: removed commented code 2024-06-07 08:54:59 +02:00
camoroso 985eb3d19d Merge branch 'main' into feat_plugin 2024-06-06 05:33:56 +02:00
camoroso 45734ab393 new test file t_iter-list.go 2024-06-06 05:33:35 +02:00
camoroso c100cf349d some identier exported; new file import-utils.go 2024-06-06 05:31:35 +02:00
117 changed files with 4184 additions and 2310 deletions
+38 -46
View File
@@ -8,12 +8,6 @@ import (
"strings" "strings"
) )
type Expr interface {
Eval(ctx ExprContext) (result any, err error)
eval(ctx ExprContext, preset bool) (result any, err error)
String() string
}
//-------- ast //-------- ast
type ast struct { type ast struct {
@@ -25,65 +19,69 @@ func NewAst() *ast {
return &ast{} return &ast{}
} }
func (self *ast) ToForest() { func (expr *ast) TypeName() string {
if self.root != nil { return "Expression"
if self.forest == nil { }
self.forest = make([]*term, 0)
func (expr *ast) ToForest() {
if expr.root != nil {
if expr.forest == nil {
expr.forest = make([]*term, 0)
} }
self.forest = append(self.forest, self.root) expr.forest = append(expr.forest, expr.root)
self.root = nil expr.root = nil
} }
} }
func (self *ast) String() string { func (expr *ast) String() string {
var sb strings.Builder var sb strings.Builder
if self.root == nil { if expr.root == nil {
sb.WriteString("(nil)") sb.WriteString("(nil)")
} else { } else {
self.root.toString(&sb) expr.root.toString(&sb)
} }
return sb.String() return sb.String()
} }
func (self *ast) addTokens(tokens ...*Token) (err error) { func (expr *ast) addTokens(tokens ...*Token) (err error) {
for _, tk := range tokens { for _, tk := range tokens {
if err = self.addToken(tk); err != nil { if _, err = expr.addToken(tk); err != nil {
break break
} }
} }
return return
} }
func (self *ast) addToken(tk *Token) (err error) { // func (expr *ast) addToken(tk *Token) (err error) {
_, err = self.addToken2(tk) // _, err = expr.addToken2(tk)
return // return
} // }
func (self *ast) addToken2(tk *Token) (t *term, err error) { func (expr *ast) addToken(tk *Token) (t *term, err error) {
if t = newTerm(tk); t != nil { if t = newTerm(tk); t != nil {
err = self.addTerm(t) err = expr.addTerm(t)
} else { } else {
err = tk.Errorf("unexpected token %q", tk.String()) err = tk.Errorf("unexpected token %q", tk.String())
} }
return return
} }
func (self *ast) addTerm(node *term) (err error) { func (expr *ast) addTerm(node *term) (err error) {
if self.root == nil { if expr.root == nil {
self.root = node expr.root = node
} else { } else {
self.root, err = self.insert(self.root, node) expr.root, err = expr.insert(expr.root, node)
} }
return return
} }
func (self *ast) insert(tree, node *term) (root *term, err error) { func (expr *ast) insert(tree, node *term) (root *term, err error) {
if tree.getPriority() < node.getPriority() { if tree.getPriority() < node.getPriority() {
root = tree root = tree
if tree.isComplete() { if tree.isComplete() {
var subRoot *term var subRoot *term
last := tree.removeLastChild() last := tree.removeLastChild()
if subRoot, err = self.insert(last, node); err == nil { if subRoot, err = expr.insert(last, node); err == nil {
subRoot.setParent(tree) subRoot.setParent(tree)
} }
} else { } else {
@@ -98,26 +96,20 @@ func (self *ast) insert(tree, node *term) (root *term, err error) {
return return
} }
func (self *ast) Finish() { func (expr *ast) Finish() {
if self.root == nil && self.forest != nil && len(self.forest) >= 1 { if expr.root == nil && expr.forest != nil && len(expr.forest) >= 1 {
self.root = self.forest[len(self.forest)-1] expr.root = expr.forest[len(expr.forest)-1]
self.forest = self.forest[0 : len(self.forest)-1] expr.forest = expr.forest[0 : len(expr.forest)-1]
} }
} }
func (self *ast) Eval(ctx ExprContext) (result any, err error) { func (expr *ast) Eval(ctx ExprContext) (result any, err error) {
return self.eval(ctx, true) expr.Finish()
}
func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) { if expr.root != nil {
self.Finish() // initDefaultVars(ctx)
if expr.forest != nil {
if self.root != nil { for _, root := range expr.forest {
if preset {
initDefaultVars(ctx)
}
if self.forest != nil {
for _, root := range self.forest {
if result, err = root.compute(ctx); err == nil { if result, err = root.compute(ctx); err == nil {
ctx.UnsafeSetVar(ControlLastResult, result) ctx.UnsafeSetVar(ControlLastResult, result)
} else { } else {
@@ -127,7 +119,7 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
} }
} }
if err == nil { 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) ctx.UnsafeSetVar(ControlLastResult, result)
} }
} }
+60
View File
@@ -0,0 +1,60 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// function.go
package expr
// ---- Linking with Expr functions
type exprFunctor struct {
baseFunctor
params []ExprFuncParam
expr Expr
defCtx ExprContext
}
// func newExprFunctor(e Expr, params []string, ctx ExprContext) *exprFunctor {
// return &exprFunctor{expr: e, params: params, defCtx: ctx}
// }
func newExprFunctor(e Expr, params []ExprFuncParam, ctx ExprContext) *exprFunctor {
var defCtx ExprContext
if ctx != nil {
// if ctx.GetParent() != nil {
// defCtx = ctx.Clone()
// defCtx.SetParent(ctx)
// } else {
defCtx = ctx
// }
}
return &exprFunctor{expr: e, params: params, defCtx: defCtx}
}
func (functor *exprFunctor) TypeName() string {
return "ExprFunctor"
}
func (functor *exprFunctor) GetDefinitionContext() ExprContext {
return functor.defCtx
}
func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
// if functor.defCtx != nil {
// ctx.Merge(functor.defCtx)
// }
for i, p := range functor.params {
if i < len(args) {
arg := args[i]
if funcArg, ok := arg.(Functor); ok {
paramSpecs := funcArg.GetParams()
ctx.RegisterFunc(p.Name(), funcArg, TypeAny, paramSpecs)
} else {
ctx.UnsafeSetVar(p.Name(), arg)
}
} else {
ctx.UnsafeSetVar(p.Name(), nil)
}
}
result, err = functor.expr.Eval(ctx)
return
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// bind-go-function.go
package expr
// ---- Linking with Go functions
type golangFunctor struct {
baseFunctor
f FuncTemplate
}
func NewGolangFunctor(f FuncTemplate) *golangFunctor {
return &golangFunctor{f: f}
}
func (functor *golangFunctor) TypeName() string {
return "GoFunctor"
}
func (functor *golangFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
return functor.f(ctx, name, args)
}
+55 -27
View File
@@ -1,10 +1,11 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// func-builtins.go // builtin-base.go
package expr package expr
import ( import (
"fmt"
"math" "math"
"strconv" "strconv"
) )
@@ -67,7 +68,7 @@ func boolFunc(ctx ExprContext, name string, args []any) (result any, err error)
case string: case string:
result = len(v) > 0 result = len(v) > 0
default: default:
err = errCantConvert(name, v, "bool") err = ErrCantConvert(name, v, "bool")
} }
return return
} }
@@ -90,7 +91,7 @@ func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result = int64(i) result = int64(i)
} }
default: default:
err = errCantConvert(name, v, "int") err = ErrCantConvert(name, v, "int")
} }
return return
} }
@@ -115,7 +116,33 @@ func decFunc(ctx ExprContext, name string, args []any) (result any, err error) {
case *FractionType: case *FractionType:
result = v.toFloat() result = v.toFloat()
default: default:
err = errCantConvert(name, v, "float") err = ErrCantConvert(name, v, "float")
}
return
}
func stringFunc(ctx ExprContext, name string, args []any) (result any, err error) {
switch v := args[0].(type) {
case int64:
result = strconv.FormatInt(v, 10)
case float64:
result = strconv.FormatFloat(v, 'g', -1, 64)
case bool:
if v {
result = "true"
} else {
result = "false"
}
case string:
result = v
case *FractionType:
result = v.ToString(0)
case Formatter:
result = v.ToString(0)
case fmt.Stringer:
result = v.String()
default:
err = ErrCantConvert(name, v, "string")
} }
return return
} }
@@ -127,9 +154,9 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
if len(args) > 1 { if len(args) > 1 {
var ok bool var ok bool
if den, ok = args[1].(int64); !ok { if den, ok = args[1].(int64); !ok {
err = errExpectedGot(name, "integer", args[1]) err = ErrExpectedGot(name, "integer", args[1])
} else if den == 0 { } else if den == 0 {
err = errFuncDivisionByZero(name) err = ErrFuncDivisionByZero(name)
} }
} }
if err == nil { if err == nil {
@@ -148,39 +175,40 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error)
case *FractionType: case *FractionType:
result = v result = v
default: default:
err = errCantConvert(name, v, "float") err = ErrCantConvert(name, v, "float")
} }
return 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 // return
} // }
func ImportBuiltinsFuncs(ctx ExprContext) { func ImportBuiltinsFuncs(ctx ExprContext) {
anyParams := []ExprFuncParam{ anyParams := []ExprFuncParam{
newFuncParam(paramValue), NewFuncParam(ParamValue),
} }
ctx.RegisterFunc("isNil", newGolangFunctor(isNilFunc), typeBoolean, anyParams) ctx.RegisterFunc("isNil", NewGolangFunctor(isNilFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isInt", newGolangFunctor(isIntFunc), typeBoolean, anyParams) ctx.RegisterFunc("isInt", NewGolangFunctor(isIntFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isFloat", newGolangFunctor(isFloatFunc), typeBoolean, anyParams) ctx.RegisterFunc("isFloat", NewGolangFunctor(isFloatFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isBool", newGolangFunctor(isBoolFunc), typeBoolean, anyParams) ctx.RegisterFunc("isBool", NewGolangFunctor(isBoolFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isString", newGolangFunctor(isStringFunc), typeBoolean, anyParams) ctx.RegisterFunc("isString", NewGolangFunctor(isStringFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isFract", newGolangFunctor(isFractionFunc), typeBoolean, anyParams) ctx.RegisterFunc("isFract", NewGolangFunctor(isFractionFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isRational", newGolangFunctor(isRationalFunc), typeBoolean, anyParams) ctx.RegisterFunc("isRational", NewGolangFunctor(isRationalFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isList", newGolangFunctor(isListFunc), typeBoolean, anyParams) ctx.RegisterFunc("isList", NewGolangFunctor(isListFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("isDict", newGolangFunctor(isDictionaryFunc), typeBoolean, anyParams) ctx.RegisterFunc("isDict", NewGolangFunctor(isDictionaryFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("bool", newGolangFunctor(boolFunc), typeBoolean, anyParams) ctx.RegisterFunc("bool", NewGolangFunctor(boolFunc), TypeBoolean, anyParams)
ctx.RegisterFunc("int", newGolangFunctor(intFunc), typeInt, anyParams) ctx.RegisterFunc("int", NewGolangFunctor(intFunc), TypeInt, anyParams)
ctx.RegisterFunc("dec", newGolangFunctor(decFunc), typeFloat, anyParams) ctx.RegisterFunc("dec", NewGolangFunctor(decFunc), TypeFloat, anyParams)
ctx.RegisterFunc("fract", newGolangFunctor(fractFunc), typeFraction, []ExprFuncParam{ ctx.RegisterFunc("string", NewGolangFunctor(stringFunc), TypeString, anyParams)
newFuncParam(paramValue), ctx.RegisterFunc("fract", NewGolangFunctor(fractFunc), TypeFraction, []ExprFuncParam{
newFuncParamFlagDef("denominator", pfOptional, 1), NewFuncParam(ParamValue),
NewFuncParamFlagDef("denominator", PfDefault, 1),
}) })
} }
func init() { func init() {
registerImport("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.") RegisterBuiltinModule("base", ImportBuiltinsFuncs, "Base expression tools like isNil(), int(), etc.")
} }
+51
View File
@@ -0,0 +1,51 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-fmt.go
package expr
import (
"fmt"
"io"
"os"
)
func getStdout(ctx ExprContext) io.Writer {
var w io.Writer
if wany, exists := ctx.GetVar(ControlStdout); exists && wany != nil {
w, _ = wany.(io.Writer)
}
if w == nil {
w = os.Stdout
}
return w
}
func printFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var n int
if n, err = fmt.Fprint(getStdout(ctx), args...); err == nil {
result = int64(n)
}
return
}
func printLnFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var n int
if n, err = fmt.Fprintln(getStdout(ctx), args...); err == nil {
result = int64(n)
}
return
}
func ImportFmtFuncs(ctx ExprContext) {
ctx.RegisterFunc("print", NewGolangFunctor(printFunc), TypeInt, []ExprFuncParam{
NewFuncParamFlag(ParamItem, PfRepeat),
})
ctx.RegisterFunc("println", NewGolangFunctor(printLnFunc), TypeInt, []ExprFuncParam{
NewFuncParamFlag(ParamItem, PfRepeat),
})
}
func init() {
RegisterBuiltinModule("fmt", ImportFmtFuncs, "String and console formatting functions")
}
+75
View File
@@ -0,0 +1,75 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtin-import.go
package expr
import (
"io"
"os"
)
func importFunc(ctx ExprContext, name string, args []any) (result any, err error) {
return importGeneral(ctx, name, args)
}
func importAllFunc(ctx ExprContext, name string, args []any) (result any, err error) {
CtrlEnable(ctx, control_export_all)
return importGeneral(ctx, name, args)
}
func importGeneral(ctx ExprContext, name string, args []any) (result any, err error) {
dirList := buildSearchDirList("sources", ENV_EXPR_SOURCE_PATH)
result, err = doImport(ctx, name, dirList, NewArrayIterator(args))
return
}
func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (result any, err error) {
var v any
var sourceFilepath string
for v, err = it.Next(); err == nil; v, err = it.Next() {
if err = checkStringParamExpected(name, v, it.Index()); err != nil {
break
}
if sourceFilepath, err = makeFilepath(v.(string), dirList); err != nil {
break
}
var file *os.File
if file, err = os.Open(sourceFilepath); err == nil {
defer file.Close()
var expr *ast
scanner := NewScanner(file, DefaultTranslations())
parser := NewParser()
if expr, err = parser.parseGeneral(scanner, true, true); err == nil {
result, err = expr.Eval(ctx)
}
if err != nil {
break
}
} else {
break
}
}
if err != nil {
if err == io.EOF {
err = nil
} else {
result = nil
}
}
return
}
func ImportImportFuncs(ctx ExprContext) {
ctx.RegisterFunc("import", NewGolangFunctor(importFunc), TypeAny, []ExprFuncParam{
NewFuncParamFlag(ParamFilepath, PfRepeat),
})
ctx.RegisterFunc("importAll", NewGolangFunctor(importAllFunc), TypeAny, []ExprFuncParam{
NewFuncParamFlag(ParamFilepath, PfRepeat),
})
}
func init() {
RegisterBuiltinModule("import", ImportImportFuncs, "Functions import() and include()")
}
+103
View File
@@ -0,0 +1,103 @@
// 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 []any) (it Iterator, op Functor, err error) {
var ok bool
if it, ok = args[0].(Iterator); !ok {
err = fmt.Errorf("paramter %q must be an iterator, passed %v [%s]", ParamIterator, args[0], TypeName(args[0]))
return
}
if len(args) > 1 {
if op, ok = args[1].(Functor); !ok || op == nil {
err = fmt.Errorf("paramter %q must be a function, passed %v [%s]", iterParamOperator, args[1], TypeName(args[1]))
return
}
if len(args) > 2 {
var vars *DictType
if vars, ok = args[2].(*DictType); !ok || vars == nil {
err = fmt.Errorf("paramter %q must be a dictionary, passed %v [%s]", iterParamVars, args[2], TypeName(args[2]))
return
}
for key, value := range *vars {
var varName string
if varName, ok = key.(string); ok {
localCtx.UnsafeSetVar(varName, value)
}
}
}
}
return
}
func runFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var it Iterator
var ok bool
var op Functor
var v any
var usingDefaultOp = false
var params []any
var item any
localCtx := ctx.Clone()
localCtx.UnsafeSetVar(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 = []any{item}
} else {
params = []any{it.Index(), item}
}
if v, err = op.Invoke(localCtx, iterParamOperator, params); err != nil {
break
} else {
var success bool
if success, ok = ToBool(v); !success || !ok {
break
}
}
}
if err == io.EOF {
err = nil
}
if err == nil {
result, _ = localCtx.GetVar(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")
}
+10 -10
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// funcs-math.go // builtin-math-arith.go
package expr package expr
import ( import (
@@ -34,8 +34,8 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result
if v, err = doAdd(ctx, name, subIter, count, level); err != nil { if v, err = doAdd(ctx, name, subIter, count, level); err != nil {
break break
} }
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(cleanName) { if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(CleanName) {
if _, err = extIter.CallOperation(cleanName, nil); err != nil { if _, err = extIter.CallOperation(CleanName, nil); err != nil {
return return
} }
} }
@@ -107,8 +107,8 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result
if v, err = doMul(ctx, name, subIter, count, level); err != nil { if v, err = doMul(ctx, name, subIter, count, level); err != nil {
break break
} }
if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(cleanName) { if extIter, ok := v.(ExtIterator); ok && extIter.HasOperation(CleanName) {
if _, err = extIter.CallOperation(cleanName, nil); err != nil { if _, err = extIter.CallOperation(CleanName, nil); err != nil {
return return
} }
} }
@@ -167,15 +167,15 @@ func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
} }
func ImportMathFuncs(ctx ExprContext) { func ImportMathFuncs(ctx ExprContext) {
ctx.RegisterFunc("add", &golangFunctor{f: addFunc}, typeNumber, []ExprFuncParam{ ctx.RegisterFunc("add", &golangFunctor{f: addFunc}, TypeNumber, []ExprFuncParam{
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, int64(0)), NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, int64(0)),
}) })
ctx.RegisterFunc("mul", &golangFunctor{f: mulFunc}, typeNumber, []ExprFuncParam{ ctx.RegisterFunc("mul", &golangFunctor{f: mulFunc}, TypeNumber, []ExprFuncParam{
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, int64(1)), NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, int64(1)),
}) })
} }
func init() { func init() {
registerImport("math.arith", ImportMathFuncs, "Functions add() and mul()") RegisterBuiltinModule("math.arith", ImportMathFuncs, "Functions add() and mul()")
} }
+65 -29
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// func-os.go // builtin-os-file.go
package expr package expr
import ( import (
@@ -68,7 +68,7 @@ func createFileFunc(ctx ExprContext, name string, args []any) (result any, err e
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)} result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
} }
} else { } else {
err = errMissingFilePath("createFile") err = errMissingFilePath(name)
} }
return return
} }
@@ -80,7 +80,7 @@ func openFileFunc(ctx ExprContext, name string, args []any) (result any, err err
result = &osReader{fh: fh, reader: bufio.NewReader(fh)} result = &osReader{fh: fh, reader: bufio.NewReader(fh)}
} }
} else { } else {
err = errMissingFilePath("openFile") err = errMissingFilePath(name)
} }
return return
} }
@@ -92,7 +92,7 @@ func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err e
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)} result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
} }
} else { } else {
err = errMissingFilePath("openFile") err = errMissingFilePath(name)
} }
return return
} }
@@ -118,13 +118,13 @@ func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err er
} }
} }
if err == nil && (handle == nil || invalidFileHandle != nil) { if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle("closeFileFunc", handle) err = errInvalidFileHandle(name, handle)
} }
result = err == nil result = err == nil
return return
} }
func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func fileWriteTextFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle var handle osHandle
var invalidFileHandle any var invalidFileHandle any
var ok bool var ok bool
@@ -142,12 +142,12 @@ func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err er
} }
if err == nil && (handle == nil || invalidFileHandle != nil) { if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle("writeFileFunc", invalidFileHandle) err = errInvalidFileHandle(name, invalidFileHandle)
} }
return return
} }
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) { func fileReadTextFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle var handle osHandle
var invalidFileHandle any var invalidFileHandle any
var ok bool var ok bool
@@ -165,50 +165,86 @@ func readFileFunc(ctx ExprContext, name string, args []any) (result any, err err
limit = s[0] limit = s[0]
} }
if v, err = r.reader.ReadString(limit); err == nil { v, err = r.reader.ReadString(limit)
if len(v) > 0 && v[len(v)-1] == limit { if err == io.EOF {
err = nil
}
if len(v) > 0 {
if v[len(v)-1] == limit {
result = v[0 : len(v)-1] result = v[0 : len(v)-1]
} else { } else {
result = v result = v
} }
} }
if err == io.EOF {
err = nil
}
} else { } else {
invalidFileHandle = handle invalidFileHandle = handle
} }
} }
if err == nil && (handle == nil || invalidFileHandle != nil) { if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle("readFileFunc", invalidFileHandle) err = errInvalidFileHandle(name, invalidFileHandle)
}
return
}
func fileReadTextAllFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle
var invalidFileHandle any
var ok bool
result = nil
if handle, ok = args[0].(osHandle); !ok || args[0] == nil {
invalidFileHandle = args[0]
}
if handle != nil {
if r, ok := handle.(*osReader); ok {
var b []byte
b, err = io.ReadAll(r.reader)
result = string(b)
} else {
invalidFileHandle = handle
}
}
if err == nil && (handle == nil || invalidFileHandle != nil) {
err = errInvalidFileHandle(name, invalidFileHandle)
} }
return return
} }
func ImportOsFuncs(ctx ExprContext) { func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("openFile", newGolangFunctor(openFileFunc), typeHandle, []ExprFuncParam{ ctx.RegisterFunc("fileOpen", NewGolangFunctor(openFileFunc), TypeHandle, []ExprFuncParam{
newFuncParam(paramFilepath), NewFuncParam(ParamFilepath),
}) })
ctx.RegisterFunc("appendFile", newGolangFunctor(appendFileFunc), typeHandle, []ExprFuncParam{
newFuncParam(paramFilepath), ctx.RegisterFunc("fileAppend", NewGolangFunctor(appendFileFunc), TypeHandle, []ExprFuncParam{
NewFuncParam(ParamFilepath),
}) })
ctx.RegisterFunc("createFile", newGolangFunctor(createFileFunc), typeHandle, []ExprFuncParam{
newFuncParam(paramFilepath), ctx.RegisterFunc("fileCreate", NewGolangFunctor(createFileFunc), TypeHandle, []ExprFuncParam{
NewFuncParam(ParamFilepath),
}) })
ctx.RegisterFunc("writeFile", newGolangFunctor(writeFileFunc), typeInt, []ExprFuncParam{
newFuncParam(typeHandle), ctx.RegisterFunc("fileClose", NewGolangFunctor(closeFileFunc), TypeBoolean, []ExprFuncParam{
newFuncParamFlagDef(typeItem, pfOptional|pfRepeat, ""), NewFuncParam(TypeHandle),
}) })
ctx.RegisterFunc("readFile", newGolangFunctor(readFileFunc), typeString, []ExprFuncParam{
newFuncParam(typeHandle), ctx.RegisterFunc("fileWriteText", NewGolangFunctor(fileWriteTextFunc), TypeInt, []ExprFuncParam{
newFuncParamFlagDef("limitCh", pfOptional, "\n"), NewFuncParam(TypeHandle),
NewFuncParamFlagDef(TypeItem, PfDefault|PfRepeat, ""),
}) })
ctx.RegisterFunc("closeFile", newGolangFunctor(closeFileFunc), typeBoolean, []ExprFuncParam{
newFuncParam(typeHandle), ctx.RegisterFunc("fileReadText", NewGolangFunctor(fileReadTextFunc), TypeString, []ExprFuncParam{
NewFuncParam(TypeHandle),
NewFuncParamFlagDef("limitCh", PfDefault, "\n"),
})
ctx.RegisterFunc("fileReadTextAll", NewGolangFunctor(fileReadTextAllFunc), TypeString, []ExprFuncParam{
NewFuncParam(TypeHandle),
}) })
} }
func init() { func init() {
registerImport("os.file", ImportOsFuncs, "Operating system file functions") RegisterBuiltinModule("os.file", ImportOsFuncs, "Operating system file functions")
} }
+34 -34
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// func-string.go // builtin-string.go
package expr package expr
import ( import (
@@ -21,11 +21,11 @@ func doJoinStr(funcName string, sep string, it Iterator) (result any, err error)
if s, ok := v.(string); ok { if s, ok := v.(string); ok {
sb.WriteString(s) sb.WriteString(s)
} else { } else {
err = errExpectedGot(funcName, typeString, v) err = ErrExpectedGot(funcName, TypeString, v)
return return
} }
} }
if err == nil || err == io.EOF { if err == io.EOF {
err = nil err = nil
result = sb.String() result = sb.String()
} }
@@ -45,13 +45,13 @@ func joinStrFunc(ctx ExprContext, name string, args []any) (result any, err erro
} else if it, ok := args[1].(Iterator); ok { } else if it, ok := args[1].(Iterator); ok {
result, err = doJoinStr(name, sep, it) result, err = doJoinStr(name, sep, it)
} else { } else {
err = errInvalidParameterValue(name, paramParts, args[1]) err = ErrInvalidParameterValue(name, ParamParts, args[1])
} }
} else { } else {
result, err = doJoinStr(name, sep, NewArrayIterator(args[1:])) result, err = doJoinStr(name, sep, NewArrayIterator(args[1:]))
} }
} else { } else {
err = errWrongParamType(name, paramSeparator, typeString, args[0]) err = ErrWrongParamType(name, ParamSeparator, TypeString, args[0])
} }
return return
} }
@@ -63,14 +63,14 @@ func subStrFunc(ctx ExprContext, name string, args []any) (result any, err error
var ok bool var ok bool
if source, ok = args[0].(string); !ok { if source, ok = args[0].(string); !ok {
return nil, errWrongParamType(name, paramSource, typeString, args[0]) return nil, ErrWrongParamType(name, ParamSource, TypeString, args[0])
} }
if start, err = toInt(args[1], name+"()"); err != nil { if start, err = ToGoInt(args[1], name+"()"); err != nil {
return return
} }
if count, err = toInt(args[2], name+"()"); err != nil { if count, err = ToGoInt(args[2], name+"()"); err != nil {
return return
} }
@@ -91,7 +91,7 @@ func trimStrFunc(ctx ExprContext, name string, args []any) (result any, err erro
var ok bool var ok bool
if source, ok = args[0].(string); !ok { if source, ok = args[0].(string); !ok {
return nil, errWrongParamType(name, paramSource, typeString, args[0]) return nil, ErrWrongParamType(name, ParamSource, TypeString, args[0])
} }
result = strings.TrimSpace(source) result = strings.TrimSpace(source)
return return
@@ -104,7 +104,7 @@ func startsWithStrFunc(ctx ExprContext, name string, args []any) (result any, er
result = false result = false
if source, ok = args[0].(string); !ok { if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0]) return result, ErrWrongParamType(name, ParamSource, TypeString, args[0])
} }
for i, targetSpec := range args[1:] { for i, targetSpec := range args[1:] {
if target, ok := targetSpec.(string); ok { if target, ok := targetSpec.(string); ok {
@@ -127,7 +127,7 @@ func endsWithStrFunc(ctx ExprContext, name string, args []any) (result any, err
result = false result = false
if source, ok = args[0].(string); !ok { if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0]) return result, ErrWrongParamType(name, ParamSource, TypeString, args[0])
} }
for i, targetSpec := range args[1:] { for i, targetSpec := range args[1:] {
if target, ok := targetSpec.(string); ok { if target, ok := targetSpec.(string); ok {
@@ -150,7 +150,7 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
var ok bool var ok bool
if source, ok = args[0].(string); !ok { if source, ok = args[0].(string); !ok {
return result, errWrongParamType(name, paramSource, typeString, args[0]) return result, ErrWrongParamType(name, ParamSource, TypeString, args[0])
} }
if sep, ok = args[1].(string); !ok { if sep, ok = args[1].(string); !ok {
@@ -182,42 +182,42 @@ func splitStrFunc(ctx ExprContext, name string, args []any) (result any, err err
// Import above functions in the context // Import above functions in the context
func ImportStringFuncs(ctx ExprContext) { func ImportStringFuncs(ctx ExprContext) {
ctx.RegisterFunc("joinStr", newGolangFunctor(joinStrFunc), typeString, []ExprFuncParam{ ctx.RegisterFunc("strJoin", NewGolangFunctor(joinStrFunc), TypeString, []ExprFuncParam{
newFuncParam(paramSeparator), NewFuncParam(ParamSeparator),
newFuncParamFlag(paramItem, pfRepeat), NewFuncParamFlag(ParamItem, PfRepeat),
}) })
ctx.RegisterFunc("subStr", newGolangFunctor(subStrFunc), typeString, []ExprFuncParam{ ctx.RegisterFunc("strSub", NewGolangFunctor(subStrFunc), TypeString, []ExprFuncParam{
newFuncParam(paramSource), NewFuncParam(ParamSource),
newFuncParamFlagDef(paramStart, pfOptional, int64(0)), NewFuncParamFlagDef(ParamStart, PfDefault, int64(0)),
newFuncParamFlagDef(paramCount, pfOptional, int64(-1)), NewFuncParamFlagDef(ParamCount, PfDefault, int64(-1)),
}) })
ctx.RegisterFunc("splitStr", newGolangFunctor(splitStrFunc), "list of "+typeString, []ExprFuncParam{ ctx.RegisterFunc("strSplit", NewGolangFunctor(splitStrFunc), "list of "+TypeString, []ExprFuncParam{
newFuncParam(paramSource), NewFuncParam(ParamSource),
newFuncParamFlagDef(paramSeparator, pfOptional, ""), NewFuncParamFlagDef(ParamSeparator, PfDefault, ""),
newFuncParamFlagDef(paramCount, pfOptional, int64(-1)), NewFuncParamFlagDef(ParamCount, PfDefault, int64(-1)),
}) })
ctx.RegisterFunc("trimStr", newGolangFunctor(trimStrFunc), typeString, []ExprFuncParam{ ctx.RegisterFunc("strTrim", NewGolangFunctor(trimStrFunc), TypeString, []ExprFuncParam{
newFuncParam(paramSource), NewFuncParam(ParamSource),
}) })
ctx.RegisterFunc("startsWithStr", newGolangFunctor(startsWithStrFunc), typeBoolean, []ExprFuncParam{ ctx.RegisterFunc("strStartsWith", NewGolangFunctor(startsWithStrFunc), TypeBoolean, []ExprFuncParam{
newFuncParam(paramSource), NewFuncParam(ParamSource),
newFuncParam(paramPrefix), NewFuncParam(ParamPrefix),
newFuncParamFlag("other "+paramPrefix, pfRepeat), NewFuncParamFlag("other "+ParamPrefix, PfRepeat),
}) })
ctx.RegisterFunc("endsWithStr", newGolangFunctor(endsWithStrFunc), typeBoolean, []ExprFuncParam{ ctx.RegisterFunc("strEndsWith", NewGolangFunctor(endsWithStrFunc), TypeBoolean, []ExprFuncParam{
newFuncParam(paramSource), NewFuncParam(ParamSource),
newFuncParam(paramSuffix), NewFuncParam(ParamSuffix),
newFuncParamFlag("other "+paramSuffix, pfRepeat), NewFuncParamFlag("other "+ParamSuffix, PfRepeat),
}) })
} }
// Register the import function in the import-register. // Register the import function in the import-register.
// That will allow to import all function of this module by the "builtin" operator." // That will allow to import all function of this module by the "builtin" operator."
func init() { func init() {
registerImport("string", ImportStringFuncs, "string utilities") RegisterBuiltinModule("string", ImportStringFuncs, "string utilities")
} }
+48
View File
@@ -0,0 +1,48 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// builtins-register.go
package expr
import (
"fmt"
)
type builtinModule struct {
importFunc func(ExprContext)
description string
imported bool
}
func newBuiltinModule(importFunc func(ExprContext), description string) *builtinModule {
return &builtinModule{importFunc, description, false}
}
var builtinModuleRegister map[string]*builtinModule
func RegisterBuiltinModule(name string, importFunc func(ExprContext), description string) {
if builtinModuleRegister == nil {
builtinModuleRegister = make(map[string]*builtinModule)
}
if _, exists := builtinModuleRegister[name]; exists {
panic(fmt.Errorf("module %q already registered", name))
}
builtinModuleRegister[name] = newBuiltinModule(importFunc, description)
}
func IterateBuiltinModules(op func(name, description string, imported bool) bool) {
if op != nil {
for name, mod := range builtinModuleRegister {
if !op(name, mod.description, mod.imported) {
break
}
}
}
}
// ----
func init() {
if builtinModuleRegister == nil {
builtinModuleRegister = make(map[string]*builtinModule)
}
}
+9 -9
View File
@@ -18,17 +18,17 @@ func NewByteSlider(size int) *ByteSlider {
} }
} }
func (self *ByteSlider) PushEnd(b byte) { func (slider *ByteSlider) PushEnd(b byte) {
if self.length == cap(self.buf) { if slider.length == cap(slider.buf) {
self.length-- slider.length--
for i := 0; i < self.length; i++ { for i := 0; i < slider.length; i++ {
self.buf[i] = self.buf[i+1] slider.buf[i] = slider.buf[i+1]
} }
} }
self.buf[self.length] = b slider.buf[slider.length] = b
self.length++ slider.length++
} }
func (self *ByteSlider) Equal(target []byte) bool { func (slider *ByteSlider) Equal(target []byte) bool {
return target != nil && bytes.Equal(self.buf, target) return target != nil && bytes.Equal(slider.buf, target)
} }
+22 -16
View File
@@ -8,7 +8,7 @@ import (
"fmt" "fmt"
) )
func errTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error) { func ErrTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error) {
if maxArgs < 0 { if maxArgs < 0 {
err = fmt.Errorf("%s(): too few params -- expected %d or more, got %d", funcName, minArgs, argCount) err = fmt.Errorf("%s(): too few params -- expected %d or more, got %d", funcName, minArgs, argCount)
} else { } else {
@@ -17,39 +17,45 @@ func errTooFewParams(funcName string, minArgs, maxArgs, argCount int) (err error
return return
} }
func errTooMuchParams(funcName string, maxArgs, argCount int) (err error) { func ErrTooMuchParams(funcName string, maxArgs, argCount int) (err error) {
err = fmt.Errorf("%s(): too much params -- expected %d, got %d", funcName, maxArgs, argCount) err = fmt.Errorf("%s(): too much params -- expected %d, got %d", funcName, maxArgs, argCount)
return return
} }
// --- General errors // --- General errors
func errCantConvert(funcName string, value any, kind string) error { func ErrCantConvert(funcName string, value any, kind string) error {
return fmt.Errorf("%s(): can't convert %s to %s", funcName, typeName(value), kind) return fmt.Errorf("%s(): can't convert %s to %s", funcName, TypeName(value), kind)
} }
func errExpectedGot(funcName string, kind string, value any) error { func ErrExpectedGot(funcName string, kind string, value any) error {
return fmt.Errorf("%s() expected %s, got %s (%v)", funcName, kind, typeName(value), value) return fmt.Errorf("%s(): expected %s, got %s (%v)", funcName, kind, TypeName(value), value)
} }
func errFuncDivisionByZero(funcName string) error { func ErrFuncDivisionByZero(funcName string) error {
return fmt.Errorf("%s(): division by zero", funcName) return fmt.Errorf("%s(): division by zero", funcName)
} }
func errDivisionByZero() error { // func ErrDivisionByZero() error {
return fmt.Errorf("division by zero") // return fmt.Errorf("division by zero")
} // }
// --- Parameter errors // --- Parameter errors
func errMissingRequiredParameter(funcName, paramName string) error { // func ErrMissingRequiredParameter(funcName, paramName string) error {
return fmt.Errorf("%s() missing required parameter %q", funcName, paramName) // return fmt.Errorf("%s(): missing required parameter %q", funcName, paramName)
// }
func ErrInvalidParameterValue(funcName, paramName string, paramValue any) error {
return fmt.Errorf("%s(): invalid value %s (%v) for parameter %q", funcName, TypeName(paramValue), paramValue, paramName)
} }
func errInvalidParameterValue(funcName, paramName string, paramValue any) error { func ErrWrongParamType(funcName, paramName, paramType string, paramValue any) error {
return fmt.Errorf("%s() invalid value %s (%v) for parameter %q", funcName, typeName(paramValue), paramValue, paramName) return fmt.Errorf("%s(): the %q parameter must be a %s, got a %s (%v)", funcName, paramName, paramType, TypeName(paramValue), paramValue)
} }
func errWrongParamType(funcName, paramName, paramType string, paramValue any) error { // --- Operator errors
return fmt.Errorf("%s() the %q parameter must be a %s, got a %s (%v)", funcName, paramName, paramType, typeName(paramValue), paramValue)
func ErrLeftOperandMustBeVariable(leftTerm, opTerm *term) error {
return leftTerm.Errorf("left operand of %q must be a variable", opTerm.source())
} }
+20 -13
View File
@@ -5,17 +5,24 @@
package expr package expr
const ( const (
paramCount = "count" ParamCount = "count"
paramItem = "item" ParamItem = "item"
paramParts = "parts" ParamParts = "parts"
paramSeparator = "separator" ParamSeparator = "separator"
paramSource = "source" ParamSource = "source"
paramSuffix = "suffix" ParamSuffix = "suffix"
paramPrefix = "prefix" ParamPrefix = "prefix"
paramStart = "start" ParamStart = "start"
paramEnd = "end" ParamEnd = "end"
paramValue = "value" ParamValue = "value"
paramEllipsis = "..." ParamName = "name"
paramFilepath = "filepath" ParamEllipsis = "..."
paramDirpath = "dirpath" ParamFilepath = "filepath"
ParamDirpath = "dirpath"
ParamIterator = "iterator"
)
// to be moved in its own source file
const (
ConstLastIndex = 0xFFFF_FFFF
) )
+12 -10
View File
@@ -5,14 +5,16 @@
package expr package expr
const ( const (
typeAny = "any" TypeAny = "any"
typeBoolean = "boolean" TypeBoolean = "boolean"
typeFloat = "float" TypeFloat = "float"
typeFraction = "fraction" TypeFraction = "fraction"
typeHandle = "handle" TypeHandle = "handle"
typeInt = "integer" TypeInt = "integer"
typeItem = "item" TypeItem = "item"
typeNumber = "number" TypeNumber = "number"
typePair = "pair" TypePair = "pair"
typeString = "string" TypeString = "string"
TypeListOf = "list-of-"
TypeListOfStrings = "list-of-strings"
) )
+7 -3
View File
@@ -22,13 +22,11 @@ func exportFunc(ctx ExprContext, name string, info ExprFunc) {
if name[0] == '@' { if name[0] == '@' {
name = name[1:] name = name[1:]
} }
// ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
// ctx.RegisterFuncInfo(name, info)
ctx.RegisterFunc(name, info.Functor(), info.ReturnType(), info.Params()) ctx.RegisterFunc(name, info.Functor(), info.ReturnType(), info.Params())
} }
func exportObjects(destCtx, sourceCtx ExprContext) { func exportObjects(destCtx, sourceCtx ExprContext) {
exportAll := isEnabled(sourceCtx, control_export_all) exportAll := CtrlIsEnabled(sourceCtx, control_export_all)
// fmt.Printf("Exporting from sourceCtx [%p] to destCtx [%p] -- exportAll=%t\n", sourceCtx, destCtx, exportAll) // fmt.Printf("Exporting from sourceCtx [%p] to destCtx [%p] -- exportAll=%t\n", sourceCtx, destCtx, exportAll)
// Export variables // Export variables
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) { for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
@@ -43,3 +41,9 @@ func exportObjects(destCtx, sourceCtx ExprContext) {
} }
} }
} }
func exportObjectsToParent(sourceCtx ExprContext) {
if parentCtx := sourceCtx.GetParent(); parentCtx != nil {
exportObjects(parentCtx, sourceCtx)
}
}
+18 -40
View File
@@ -4,13 +4,14 @@
// control.go // control.go
package expr package expr
import "strings"
// Preset control variables // Preset control variables
const ( const (
ControlLastResult = "last" ControlPreset = "_preset"
ControlBoolShortcut = "_bool_shortcut" ControlLastResult = "last"
ControlImportPath = "_import_path" ControlBoolShortcut = "_bool_shortcut"
ControlSearchPath = "_search_path"
ControlParentContext = "_parent_context"
ControlStdout = "_stdout"
) )
// Other control variables // Other control variables
@@ -20,43 +21,20 @@ const (
// Initial values // Initial values
const ( const (
init_import_path = "~/.local/lib/go-pkg/expr:/usr/local/lib/go-pkg/expr:/usr/lib/go-pkg/expr" init_search_path = "~/.local/lib/go-pkg/expr:/usr/local/lib/go-pkg/expr:/usr/lib/go-pkg/expr"
) )
func SetCtrl(ctx ExprContext, name string, value any) (current any) {
current, _ = ctx.GetVar(name)
ctx.UnsafeSetVar(name, value)
return
}
func initDefaultVars(ctx ExprContext) { func initDefaultVars(ctx ExprContext) {
ctx.SetVar(ControlBoolShortcut, true) if _, exists := ctx.GetVar(ControlPreset); exists {
ctx.SetVar(ControlImportPath, init_import_path) return
}
func enable(ctx ExprContext, name string) {
if strings.HasPrefix(name, "_") {
ctx.SetVar(name, true)
} else {
ctx.SetVar("_"+name, true)
} }
} ctx.UnsafeSetVar(ControlPreset, true)
ctx.UnsafeSetVar(ControlBoolShortcut, true)
func disable(ctx ExprContext, name string) { ctx.UnsafeSetVar(ControlSearchPath, init_search_path)
if strings.HasPrefix(name, "_") {
ctx.SetVar(name, false)
} else {
ctx.SetVar("_"+name, false)
}
}
func isEnabled(ctx ExprContext, name string) (status bool) {
if v, exists := ctx.GetVar(name); exists {
if b, ok := v.(bool); ok {
status = b
}
}
return
}
func getControlString(ctx ExprContext, name string) (s string, exists bool) {
var v any
if v, exists = ctx.GetVar(name); exists {
s, exists = v.(string)
}
return
} }
+117 -38
View File
@@ -5,30 +5,49 @@
package expr package expr
import ( import (
"errors"
"io" "io"
) )
type dataCursor struct { type dataCursor struct {
ds map[string]Functor ds map[string]Functor
ctx ExprContext ctx ExprContext
index int initState bool // true if no item has prodiced yet (this replace di initial Next() call in the contructor)
resource any index int
nextFunc Functor count int
cleanFunc Functor current any
resetFunc Functor lastErr error
currentFunc Functor resource any
nextFunc Functor
cleanFunc Functor
resetFunc Functor
} }
func newDataCursor(ctx ExprContext, ds map[string]Functor) (dc *dataCursor) { func NewDataCursor(ctx ExprContext, ds map[string]Functor, resource any) (dc *dataCursor) {
dc = &dataCursor{ dc = &dataCursor{
ds: ds, ds: ds,
index: -1, initState: true,
ctx: ctx.Clone(), index: -1,
count: 0,
current: nil,
lastErr: nil,
resource: resource,
ctx: ctx.Clone(),
nextFunc: ds[NextName],
cleanFunc: ds[CleanName],
resetFunc: ds[ResetName],
} }
//dc.Next()
return return
} }
func (dc *dataCursor) Context() ExprContext {
return dc.ctx
}
func (dc *dataCursor) TypeName() string {
return "DataCursor"
}
// func mapToString(m map[string]Functor) string { // func mapToString(m map[string]Functor) string {
// var sb strings.Builder // var sb strings.Builder
// sb.WriteByte('{') // sb.WriteByte('{')
@@ -58,7 +77,7 @@ func (dc *dataCursor) String() string {
} }
func (dc *dataCursor) HasOperation(name string) (exists bool) { func (dc *dataCursor) HasOperation(name string) (exists bool) {
exists = name == indexName exists = name == IndexName
if !exists { if !exists {
f, ok := dc.ds[name] f, ok := dc.ds[name]
exists = ok && isFunctor(f) exists = ok && isFunctor(f)
@@ -67,7 +86,7 @@ func (dc *dataCursor) HasOperation(name string) (exists bool) {
} }
func (dc *dataCursor) CallOperation(name string, args []any) (value any, err error) { func (dc *dataCursor) CallOperation(name string, args []any) (value any, err error) {
if name == indexName { if name == IndexName {
value = int64(dc.Index()) value = int64(dc.Index())
} else if functor, ok := dc.ds[name]; ok && isFunctor(functor) { } else if functor, ok := dc.ds[name]; ok && isFunctor(functor) {
if functor == dc.cleanFunc { if functor == dc.cleanFunc {
@@ -89,15 +108,19 @@ func (dc *dataCursor) Reset() (success bool, err error) {
if dc.resetFunc != nil { if dc.resetFunc != nil {
if dc.resource != nil { if dc.resource != nil {
ctx := cloneContext(dc.ctx) ctx := cloneContext(dc.ctx)
if _, err = dc.resetFunc.Invoke(ctx, resetName, []any{dc.resource}); err == nil { _, err = dc.resetFunc.Invoke(ctx, ResetName, []any{dc.resource})
dc.index = -1
}
exportObjects(dc.ctx, ctx) exportObjects(dc.ctx, ctx)
dc.index = -1
dc.count = 0
dc.initState = true
dc.current = nil
dc.lastErr = nil
//dc.Next()
} else { } else {
err = errInvalidDataSource() err = errInvalidDataSource()
} }
} else { } else {
err = errNoOperation(resetName) err = errNoOperation(ResetName)
} }
success = err == nil success = err == nil
return return
@@ -107,48 +130,104 @@ func (dc *dataCursor) Clean() (success bool, err error) {
if dc.cleanFunc != nil { if dc.cleanFunc != nil {
if dc.resource != nil { if dc.resource != nil {
ctx := cloneContext(dc.ctx) ctx := cloneContext(dc.ctx)
_, err = dc.cleanFunc.Invoke(ctx, cleanName, []any{dc.resource}) _, err = dc.cleanFunc.Invoke(ctx, CleanName, []any{dc.resource})
dc.resource = nil // dc.resource = nil
exportObjects(dc.ctx, ctx) exportObjects(dc.ctx, ctx)
} else { } else {
err = errInvalidDataSource() err = errInvalidDataSource()
} }
} else { } else {
err = errors.New("no 'clean' function defined in the data-source") err = errNoOperation(CleanName)
} }
success = err == nil success = err == nil
return return
} }
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
ctx := cloneContext(dc.ctx) dc.init()
if item, err = dc.currentFunc.Invoke(ctx, currentName, []any{}); err == nil && item == nil {
if dc.current != nil {
item = dc.current
} else {
err = io.EOF err = io.EOF
} }
exportObjects(dc.ctx, ctx)
return return
} }
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item func (dc *dataCursor) checkFilter(filter Functor, item any) (accepted bool, err error) {
var v any
var ok bool
ctx := cloneContext(dc.ctx)
if v, err = filter.Invoke(ctx, FilterName, []any{item, dc.index}); err == nil && v != nil {
if accepted, ok = v.(bool); !ok {
accepted = true // NOTE: A non-boolean value that is not nil means the item has been accepted
}
}
return
}
func (dc *dataCursor) mapItem(mapper Functor, item any) (mappedItem any, err error) {
ctx := cloneContext(dc.ctx)
mappedItem, err = mapper.Invoke(ctx, MapName, []any{item, dc.index})
return
}
func (dc *dataCursor) 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
if dc.resource != nil { if dc.resource != nil {
ctx := cloneContext(dc.ctx) filter := dc.ds[FilterName]
// fmt.Printf("Entering Inner-Ctx [%p]: %s\n", ctx, CtxToString(ctx, 0)) mapper := dc.ds[MapName]
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{dc.resource}); err == nil { var item any
if item == nil { for item == nil && dc.lastErr == nil {
err = io.EOF ctx := cloneContext(dc.ctx)
} else { dc.index++
dc.index++ if item, dc.lastErr = dc.nextFunc.Invoke(ctx, NextName, []any{dc.resource, dc.index}); 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()
} }
// 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 { } else {
err = errInvalidDataSource() dc.lastErr = errInvalidDataSource()
} }
return return
} }
func (dc *dataCursor) Index() int { func (dc *dataCursor) Index() int {
return dc.index return dc.index - 1
}
func (dc *dataCursor) Count() int {
return dc.count
} }
+68 -32
View File
@@ -12,7 +12,13 @@ import (
type DictType map[any]any type DictType map[any]any
func newDict(dictAny map[any]*term) (dict *DictType) { func MakeDict() (dict *DictType) {
d := make(DictType)
dict = &d
return
}
func NewDict(dictAny map[any]any) (dict *DictType) {
var d DictType var d DictType
if dictAny != nil { if dictAny != nil {
d = make(DictType, len(dictAny)) d = make(DictType, len(dictAny))
@@ -26,42 +32,67 @@ func newDict(dictAny map[any]*term) (dict *DictType) {
return return
} }
func (dict *DictType) toMultiLine(sb *strings.Builder, indent int) { func newDict(dictAny map[any]*term) (dict *DictType) {
sb.WriteString(strings.Repeat("\t", indent)) // TODO Change wi a call to NewDict()
sb.WriteString("{\n") var d DictType
if dictAny != nil {
first := true d = make(DictType, len(dictAny))
for name, value := range *dict { for i, item := range dictAny {
if first { d[i] = item
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
sb.WriteString(strings.Repeat("\t", indent+1))
if key, ok := name.(string); ok {
sb.WriteString(string('"') + key + string('"'))
} else {
sb.WriteString(fmt.Sprintf("%v", name))
}
sb.WriteString(": ")
if f, ok := value.(Formatter); ok {
sb.WriteString(f.ToString(MultiLine))
} else if _, ok = value.(Functor); ok {
sb.WriteString("func(){}")
} else {
sb.WriteString(fmt.Sprintf("%v", value))
} }
} else {
d = make(DictType)
} }
sb.WriteString(strings.Repeat("\t", indent)) dict = &d
sb.WriteString("\n}") return
}
func (dict *DictType) toMultiLine(sb *strings.Builder, opt FmtOpt) {
indent := GetFormatIndent(opt)
flags := GetFormatFlags(opt)
//sb.WriteString(strings.Repeat(" ", indent))
sb.WriteByte('{')
if len(*dict) > 0 {
innerOpt := MakeFormatOptions(flags, indent+1)
nest := strings.Repeat(" ", indent+1)
sb.WriteByte('\n')
first := true
for name, value := range *dict {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
sb.WriteString(nest)
if key, ok := name.(string); ok {
sb.WriteString(string('"') + key + string('"'))
} else {
sb.WriteString(fmt.Sprintf("%v", name))
}
sb.WriteString(": ")
if f, ok := value.(Formatter); ok {
sb.WriteString(f.ToString(innerOpt))
} else if _, ok = value.(Functor); ok {
sb.WriteString("func(){}")
} else {
sb.WriteString(fmt.Sprintf("%v", value))
}
}
sb.WriteByte('\n')
sb.WriteString(strings.Repeat(" ", indent))
}
sb.WriteString("}")
} }
func (dict *DictType) ToString(opt FmtOpt) string { func (dict *DictType) ToString(opt FmtOpt) string {
var sb strings.Builder var sb strings.Builder
if opt&MultiLine != 0 { flags := GetFormatFlags(opt)
dict.toMultiLine(&sb, 0) if flags&MultiLine != 0 {
dict.toMultiLine(&sb, opt)
} else { } else {
sb.WriteByte('{') sb.WriteByte('{')
first := true first := true
@@ -124,7 +155,12 @@ func (dict *DictType) merge(second *DictType) {
} }
func (dict *DictType) setItem(key any, value any) (err error) { func (dict *DictType) setItem(key any, value any) (err error) {
(*dict)[key]=value (*dict)[key] = value
return return
} }
////////////////
type DictFormat interface {
ToDict() *DictType
}
+284 -137
View File
@@ -9,6 +9,7 @@ Expressions calculator
:icons: font :icons: font
:icon-set: fi :icon-set: fi
:numbered: :numbered:
:data-uri:
//:table-caption: Tabella //:table-caption: Tabella
//:figure-caption: Diagramma //:figure-caption: Diagramma
:docinfo1: :docinfo1:
@@ -19,21 +20,52 @@ Expressions calculator
:rouge-style: gruvbox :rouge-style: gruvbox
// :rouge-style: colorful // :rouge-style: colorful
//:rouge-style: monokay //:rouge-style: monokay
// Work around to manage double-column in back-tick quotes
:2c: ::
toc::[] toc::[]
#TODO: Work in progress (last update on 2024/06/02, 08:18 a.m.)# #TODO: Work in progress (last update on 2024/06/21, 05:40 a.m.)#
== Expr == Expr
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions. _Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
=== Concepts and terminology === Concepts and terminology
#TODO#
Expressions are texts containing sequences of operations represented by a syntax very similar to that of most programming languages. _Expr_ package provides these macro functions:
* *_Scanner_* -- Its input is a text. It scans expression text characters to produce a flow of logical symbol and related attributes, aka tokens.
* *_Parser_* -- Parser input is the token flow coming from the scanner. It analyses the token flow verifyng if it complies with the _Expr_ syntax. If that is the case, the Parser generates the Abstract Syntax Tree (AST). This is tree data structure that represents the components of an expressions and how they are related one each other.
* *_Calculator_*. Its input is the AST. It computes the parsed expression contained in the AST and returns the result or an error.
image::expression-diagram.png[] image::expression-diagram.png[]
==== Variables
_Expr_ supports variables. The result of an expression can be stored in a variable and reused in other espressions simply specifying the name of the variable as an operand.
==== Multi-expression
An input text valid for _Expr_ can contain more than an expression. Expressions are separated by [blue]`;` (semicolon). When an input contains two or more expressions it is called _multi-expression_.
_Expr_ parses and computes each expression of a multi-espression, from the left to the right. If all expressions are computed without errors, it only returns the value of the last, the right most.
The result of each expression of a multi-expression is stored in an automatic variable named _last_. In this way, each expression can refer to the result of the previous one without the need to assign that value to a new dedicated variable.
==== Calculation context
All objects, such as variables and functions, created during the calculation of an expression are stored in a memory called _context_.
The expression context is analogous to the stack-frame of other programming languages. When a function is called, a new context is allocated to store local definitions.
Function contexts are created by cloning the calling context. More details on this topic are given later in this document.
_Expr_ creates and keeps a inner _global context_ where it stores imported functions, either from builtin or plugin modules. To perform calculations, the calling program must provide its own context; this is the _main context_. All calculations take place in this context. As mentioned eralier, when a function is called, a new context is created by cloning the calling context. The createt context can be called _function context_.
Imported functions are registerd in the _global context_. When an expression first calls an imported function, that function is linked to the current context; this can be the _main context_ or a _function context_.
=== `dev-expr` test tool === `dev-expr` test tool
`dev-expr` is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, in additon to the automatic verification test suite based on the Go test framework, `dev-expr` provides an important aid for quickly testing of new features during their development. Before we begin to describe the syntax of _Expr_, it is worth introducing _dev-expr_ because it will be used to show many examples of expressions.
`dev-expr` is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, in additon to the automatic verification test suite based on the Go test framework, `dev-expr` provided an important aid for quickly testing of new features during their development.
`dev-expr` can work as a _REPL_, _**R**ead-**E**xecute-**P**rint-**L**oop_, or it can process expression acquired from files or standard input. `dev-expr` can work as a _REPL_, _**R**ead-**E**xecute-**P**rint-**L**oop_, or it can process expression acquired from files or standard input.
@@ -47,11 +79,10 @@ Here are some examples of execution.
# Type 'exit' or Ctrl+D to quit the program. # Type 'exit' or Ctrl+D to quit the program.
[user]$ ./dev-expr [user]$ ./dev-expr
expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@portale-stac.it) dev-expr -- Expressions calculator v1.10.0(build 14),2024/06/17 (celestino.amoroso@portale-stac.it)
Based on the Expr package v0.10.0 Based on the Expr package v0.19.0
Type help to get the list of available commands Type help to get the list of available commands
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
>>> help >>> help
--- REPL commands: --- REPL commands:
source -- Load a file as input source -- Load a file as input
@@ -61,6 +92,7 @@ expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@por
help -- Show command list help -- Show command list
ml -- Enable/Disable multi-line output ml -- Enable/Disable multi-line output
mods -- List builtin modules mods -- List builtin modules
output -- Enable/Disable printing expression results. Options 'on', 'off', 'status'
--- Command line options: --- Command line options:
-b <builtin> Import builtin modules. -b <builtin> Import builtin modules.
@@ -70,10 +102,11 @@ expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@por
-i Force REPL operation when all -e occurences have been processed -i Force REPL operation when all -e occurences have been processed
-h, --help, help Show this help menu -h, --help, help Show this help menu
-m, --modules List all builtin modules -m, --modules List all builtin modules
--noout Disable printing of expression results
-p Print prefix form -p Print prefix form
-t Print tree form <2> -t Print tree form <2>
-v, --version Show program version
>>> >>>
---- ----
<1> Only available for single fraction values <1> Only available for single fraction values
@@ -83,8 +116,8 @@ expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@por
[source,shell] [source,shell]
---- ----
[user]$ ./dev-expr [user]$ ./dev-expr
expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@portale-stac.it) dev-expr -- Expressions calculator v1.10.0(build 14),2024/06/17 (celestino.amoroso@portale-stac.it)
Based on the Expr package v0.10.0 Based on the Expr package v0.19.0
Type help to get the list of available commands Type help to get the list of available commands
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
@@ -101,21 +134,21 @@ expr -- Expressions calculator v1.7.1(build 2),2024/05/16 (celestino.amoroso@por
7 7
- -
6 6
>>> 1+2 but 5|2+0.5 <4> >>> 4+2 but 5|2+0.5 <4>
3 3
>>> 1+2; 5|2+0.5 <5> >>> 4+2; 5|2+0.5 <5>
3 3
>>> >>>
---- ----
<1> Number bases: 0x = hexadecimal, 0o = octal, 0b = binary. <1> Number bases: 0x = _hexadecimal_, 0o = _octal_, 0b = _binary_.
<2> Fractions: numerator | denominator. <2> Fractions: _numerator_ | _denominator_.
<3> Activate multi-line output of fractions. <3> Activate multi-line output of fractions.
<4> But operator, see <<_but_operator>>. <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. <5> Multi-expression: the same result of the previous single expression but this it is obtained with two separated calculations.
== Data types == Data types
_Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists. _Expr_ has its type system which is a subset of Golang's type system. It supports numerical, string, relational, boolean expressions, and mixed-type lists and maps.
=== Numbers === Numbers
_Expr_ supports three type of numbers: _Expr_ supports three type of numbers:
@@ -155,7 +188,7 @@ Value range: *-9223372036854775808* to *9223372036854775807*
| [blue]`*` | _product_ | Multiply two values | [blue]`-1 * 2` -> -2 | [blue]`*` | _product_ | Multiply two values | [blue]`-1 * 2` -> -2
| [blue]`/` | _Division_ | Divide the left value by the right one^(*)^ | [blue]`-10 / 2` -> 5 | [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]`%` | _Modulo_ | Remainder of the integer division | [blue]`5 % 2` -> 1
|=== |===
^(*)^ See also the _float division_ [blue]`./` below. ^(*)^ See also the _float division_ [blue]`./` below.
@@ -173,15 +206,20 @@ _dec-seq_ = _see-integer-literal-syntax_
.Examples .Examples
`>>>` [blue]`1.0` + `>>>` [blue]`1.0` +
[green]`1` + [green]`1`
`>>>` [blue]`0.123` + `>>>` [blue]`0.123` +
[green]`0.123` + [green]`0.123`
`>>>` [blue]`4.5e+3` + `>>>` [blue]`4.5e+3` +
[green]`4500` + [green]`4500`
`>>>` [blue]`4.5E-33` + `>>>` [blue]`4.5E-33` +
[green]`4.5e-33` + [green]`4.5e-33`
`>>>` [blue]`4.5E-3` + `>>>` [blue]`4.5E-3` +
[green]`0.0045` + [green]`0.0045`
`>>>` [blue]`4.5E10` + `>>>` [blue]`4.5E10` +
[green]`4.5e+10` [green]`4.5e+10`
@@ -211,35 +249,43 @@ _digit-seq_ = _see-integer-literal-syntax_
==== ====
.Examples .Examples
// [source,go]
// ----
`>>>` [blue]`1 | 2` + `>>>` [blue]`1 | 2` +
[green]`1|2` + [green]`1|2`
`>>>` [blue]`4|6` [gray]_// Fractions are always reduced to their lowest terms_ + `>>>` [blue]`4|6` [gray]_// Fractions are always reduced to their lowest terms_ +
[green]`2|3` + [green]`2|3`
`>>>` [blue]`1|2 + 2|3` + `>>>` [blue]`1|2 + 2|3` +
[green]`7|6` + [green]`7|6`
`>>>` [blue]`1|2 * 2|3` + `>>>` [blue]`1|2 * 2|3` +
[green]`1|3` + [green]`1|3`
`>>>` [blue]`1|2 / 1|3` + `>>>` [blue]`1|2 / 1|3` +
[green]`3|2` + [green]`3|2`
`>>>` [blue]`1|2 ./ 1|3` [gray]_// Force decimal division_ + `>>>` [blue]`1|2 ./ 1|3` [gray]_// Force decimal division_ +
[green]`1.5` + [green]`1.5`
`>>>` [blue]`-1|2` + `>>>` [blue]`-1|2` +
[green]`-1|2` + [green]`-1|2`
`>>>` [blue]`1|-2` [gray]_// Invalid sign specification_ + `>>>` [blue]`1|-2` [gray]_// Invalid sign specification_ +
[red]_Eval Error: [1:3] infix operator "|" requires two non-nil operands, got 1_ + [red]_Eval Error: [1:3] infix operator "|" requires two non-nil operands, got 1_
`>>>` [blue]`1|(-2)` + `>>>` [blue]`1|(-2)` +
[green]`-1|2` [green]`-1|2`
// ----
Fractions can be used together with integers and floats in expressions. Fractions can be used together with integers and floats in expressions.
.Examples .Examples
`>>>` [blue]`1|2 + 5` + `>>>` [blue]`1|2 + 5` +
[green]`11|2` + [green]`11|2`
`>>>` [blue]`4 - 1|2` + `>>>` [blue]`4 - 1|2` +
[green]`7|2` + [green]`7|2`
`>>>` [blue]`1.0 + 1|2` + `>>>` [blue]`1.0 + 1|2` +
[green]`1.5` [green]`1.5`
@@ -250,12 +296,15 @@ Strings are character sequences enclosed between two double quote [blue]`"`.
.Examples .Examples
`>>>` [blue]`"I'm a string"` + `>>>` [blue]`"I'm a string"` +
[green]`I'm a string` + [green]`I'm a string`
`>>>` [blue]`"123abc?!"` + `>>>` [blue]`"123abc?!"` +
[green]`123abc?!` + [green]`123abc?!`
`>>>` [blue]`"123\nabc"` + `>>>` [blue]`"123\nabc"` +
[green]`123` + [green]`123` +
[green]`abc` + [green]`abc`
`>>>` [blue]`"123\tabc"` + `>>>` [blue]`"123\tabc"` +
[green]`123{nbsp}{nbsp}{nbsp}{nbsp}abc` [green]`123{nbsp}{nbsp}{nbsp}{nbsp}abc`
@@ -272,25 +321,36 @@ Some arithmetic operators can also be used with strings.
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` -> _"oneone"_ | [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` -> _"oneone"_
|=== |===
The items of strings can be accessed using the dot `.` operator. The items of strings can be accessed using the square `[]` operator.
.Item access syntax .Item access syntax
==== ====
_item_ = _string-expr_ "**.**" _integer-expr_ *_item_* = _string-expr_ "**[**" _integer-expr_ "**]**"
====
.Sub-string syntax
====
*_sub-string_* = _string-expr_ "**[**" _integer-expr_ "**:**" _integer-expr_ "**]**"
==== ====
.String examples .String examples
`>>>` [blue]`s="abc"` [gray]_// assign the string to variable s_ + `>>>` [blue]`s="abcd"` [gray]_// assign the string to variable s_ +
[green]`abc` + [green]`"abcd"`
`>>>` [blue]`s.1` [gray]_// char at position 1 (starting from 0)_ +
[green]`b` + `>>>` [blue]`s[1]` [gray]_// char at position 1 (starting from 0)_ +
`>>>` [blue]`s.(-1)` [gray]_// char at position -1, the rightmost one_ + [green]`"b"`
[green]`c` +
`>>>` [blue]`s.[-1]` [gray]_// char at position -1, the rightmost one_ +
[green]`"d"`
`>>>` [blue]`\#s` [gray]_// number of chars_ + `>>>` [blue]`\#s` [gray]_// number of chars_ +
[gren]`3` + [gren]`4`
`>>>` [blue]`#"abc"` [gray]_// number of chars_ +
`>>>` [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"`
=== Booleans === Booleans
Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relational and boolean expressions result in boolean values. Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relational and boolean expressions result in boolean values.
@@ -334,14 +394,16 @@ Boolean data type has two values only: [blue]_true_ and [blue]_false_. Relationa
[CAUTION] [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 .Example
[source,go] [source,go]
---- ----
2 > (a=1) or (a=8) > 0; a // <1> 2 > (a=1) or (a=8) > 0; a // <1>
---- ----
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and as a conseguence its right value is not computed. Therefore the _a_ variable only receives the integer _1_. <1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and as a conseguence its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
TIP: `dev-expr` provides the _ctrl()_ function that allows to change this behaviour.
==== ====
=== Lists === Lists
@@ -356,13 +418,17 @@ _non-empty-list_ = "**[**" _any-value_ {"**,**" _any-value} "**]**" +
.Examples .Examples
`>>>` [blue]`[1,2,3]` [gray]_// List of integers_ + `>>>` [blue]`[1,2,3]` [gray]_// List of integers_ +
[green]`[1, 2, 3]` + [green]`[1, 2, 3]`
`>>>` [blue]`["one", "two", "three"]` [gray]_// List of strings_ + `>>>` [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_ + `>>>` [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_ + `>>>` [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_ + `>>>` [blue]`[ [1,"one"], [2,"two"]]` [gray]_// List of lists_ +
[green]`[[1, "one"], [2, "two"]]` [green]`[[1, "one"], [2, "two"]]`
@@ -375,37 +441,52 @@ _non-empty-list_ = "**[**" _any-value_ {"**,**" _any-value} "**]**" +
| [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` -> _[1,3]_ | [blue]`-` | _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]`>>` | _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]`<<` | _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]`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]`6 in [1,2,3]` -> _false_
|=== |===
The items of array can be accessed using the dot `.` operator. Array's items can be accessed using the index `[]` operator.
.Item access syntax .Item access syntax
==== ====
_item_ = _list-expr_ "**.**" _integer-expr_ *_item_* = _list-expr_ "**[**" _integer-expr_ "**]**"
====
.Sub-array (or slice of array) syntax
====
*_slice_* = _string-expr_ "**[**" _integer-expr_ "**:**" _integer-expr_ "**]**"
==== ====
.Items of list .Items of list
`>>>` [blue]`[1,2,3].1` + `>>>` [blue]`[1,2,3].1` +
[green]`2` + [green]`2`
`>>>` [blue]`list=[1,2,3]; list.1` + `>>>` [blue]`list=[1,2,3]; list.1` +
[green]`2` + [green]`2`
`>>>` [blue]`["one","two","three"].1` + `>>>` [blue]`["one","two","three"].1` +
[green]`two` + [green]`two`
`>>>` [blue]`list=["one","two","three"]; list.(2-1)` + `>>>` [blue]`list=["one","two","three"]; list.(2-1)` +
[green]`two` + [green]`two`
`>>>` [blue]`list.(-1)` + `>>>` [blue]`list.(-1)` +
[green]`three` + [green]`three`
`>>>` [blue]`list.(10)` + `>>>` [blue]`list.(10)` +
[red]`Eval Error: [1:9] index 10 out of bounds` + [red]`Eval Error: [1:9] index 10 out of bounds`
`>>>` [blue]`#list` + `>>>` [blue]`#list` +
[green]`3` + [green]`3`
`>>>` [blue]`index=2; ["a", "b", "c", "d"].index` +
`>>>` [blue]`index=2; ["a", "b", "c", "d"][index]` +
[green]`c` [green]`c`
`>>>` [blue]`["a", "b", "c", "d"][2:]` +
[green]`["c", "d"]`
=== Dictionaries === 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 is set of pairs _key/value_. It is also known as _map_ or _associative array_.
@@ -419,14 +500,11 @@ _empty-dict_ = "**{}**" +
_non-empty-dict_ = "**{**" _key-scalar_ "**:**" _any-value_ {"**,**" _key-scalar_ "**:**" _any-value} "**}**" + _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 .Dict operators
[cols="^2,^2,4,5"] [cols="^2,^2,4,5"]
@@ -434,11 +512,27 @@ _non-empty-dict_ = "**{**" _key-scalar_ "**:**" _any-value_ {"**,**" _key-scalar
| Symbol | Operation | Description | Examples | Symbol | Operation | Description | Examples
| [blue]`+` | _Join_ | Joins two dicts | [blue]`{1:"one"}+{6:"six"}` -> _{1: "one", 6: "six"}_ | [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]`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]`"six" in {"one":1, "two":2}` -> _false_
|=== |===
.Examples
`>>>` [blue]`{1:"one", 2:"two"}` +
[green]`{1: "one", 2: "two"}`
`>>>` [blue]`{"one":1, "two": 2}` +
[green]`{"one": 1, "two": 2}`
`>>>` [blue]`{"sum":1+2+3, "prod":1*2*3}` +
[green]`{"sum": 6, "prod": 6}`
`>>>` [blue]`{"one":1, "two":2}["two"]` +
[green]`2`
`>>>` [blue]`d={"one":1, "two":2}; d["six"]=6; d` +
[green]`{"two": 2, "one": 1, "six": 6}`
== Variables == Variables
_Expr_, like most programming languages, supports variables. A variable is an identifier with an assigned value. Variables are stored in _contexts_. _Expr_, like most programming languages, supports variables. A variable is an identifier with an assigned value. Variables are stored in _contexts_.
@@ -454,17 +548,23 @@ NOTE: The assign operator [blue]`=` returns the value assigned to the variable.
.Examples .Examples
`>>>` [blue]`a=1` + `>>>` [blue]`a=1` +
[green]`1` + [green]`1`
`>>>` [blue]`a_b=1+2` + `>>>` [blue]`a_b=1+2` +
[green]`1+2` + [green]`1+2`
`>>>` [blue]`a_b` + `>>>` [blue]`a_b` +
[green]`3` + [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` + `>>>` [blue]`x = 5.2 * (9-3)` [gray]_// The assigned value has the typical approximation error of the float data-type_ +
[green]`31.200000000000003`
`>>>` [blue]`x = 1; y = 2*x` + `>>>` [blue]`x = 1; y = 2*x` +
[green]`2` + [green]`2`
`>>>` [blue]`_a=2` + `>>>` [blue]`_a=2` +
[red]`Parse Error: [1:2] unexpected token "_"` + [red]`Parse Error: [1:2] unexpected token "_"`
`>>>` [blue]`1=2` + `>>>` [blue]`1=2` +
[red]`Parse Error: assign operator ("=") must be preceded by a variable` [red]`Parse Error: assign operator ("=") must be preceded by a variable`
@@ -474,6 +574,11 @@ NOTE: The assign operator [blue]`=` returns the value assigned to the variable.
=== [blue]`;` operator === [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. The semicolon operator [blue]`;` is an infixed pseudo-operator. It evaluates the left expression first and then the right expression. The value of the latter is the final result.
.Mult-expression syntax
====
*_multi-expression_* = _expression_ {"**;**" _expression_ }
====
An expression that contains [blue]`;` is called a _multi-expression_ and each component expressione is called a _sub-expression_. An expression that contains [blue]`;` is called a _multi-expression_ and each component expressione is called a _sub-expression_.
IMPORTANT: Technically [blue]`;` is not treated as a real operator. It acts as a separator in lists of expressions. IMPORTANT: Technically [blue]`;` is not treated as a real operator. It acts as a separator in lists of expressions.
@@ -532,38 +637,47 @@ The [blue]`:` symbol (colon) is the separator of the selector-cases. Note that i
.Examples .Examples
`>>>` [blue]`1 ? {"a"} : {"b"}` + `>>>` [blue]`1 ? {"a"} : {"b"}` +
[green]`b` + [green]`b`
`>>>` [blue]`10 ? {"a"} : {"b"} :: {"c"}` +
[green]`c' + `>>>` [blue]`10 ? {"a"} : {"b"} {2c} {"c"}` +
[green]`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}` + [green]`c`
[green]`b` +
`>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}` + `>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} {2c} {"c"}` +
[red]`Parse Error: [1:34] case list in default clause` + [green]`b`
[green]`>>>` [blue]`10 ? {"a"} :[10] {x="b" but x} :: {"c"}` +
[green]`b` + `>>>` [blue]`10 ? {"a"} :[true, 2+8] {"b"} {2c} [10] {"c"}` +
`>>>` [blue]`10 ? {"a"} :[10] {x="b"; x} :: {"c"}` + [red]`Parse Error: [1:34] case list in default clause`
[green]`b` +
`>>>` [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"}` + `>>>` [blue]`10 ? {"a"} : {"b"}` +
[red]`Eval Error: [1:3] no case catches the value (10) of the selection expression` [red]`Eval Error: [1:3] no case catches the value (10) of the selection expression`
=== Variable default value [blue]`??` and [blue]`?=` === Variable default value [blue]`??` and [blue]`?=`
The left operand of these two operators must be a variable. The right operator can be any expression. They return the value of the variable if this is define; otherwise they return the value of the right expression. The left operand of these two operators must be a variable. The right operator can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression.
IMPORTANT: If the left variable is defined, the right expression is not evuated at all. IMPORTANT: If the left variable is defined, the right expression is not evaluated at all.
The [blue]`??` do not change the status of the left variable. The [blue]`??` operator do not change the status of the left variable.
The [blue]`?=` assigns the calculated value of the right expression to the left variable. The [blue]`?=` assigns the calculated value of the right expression to the left variable.
.Examples .Examples
`>>>` [blue]`var ?? (1+2)`' `>>>` [blue]`var ?? (1+2)`' +
[green]`3` + [green]`3`
`>>>` [blue]`var` + `>>>` [blue]`var` +
[red]`Eval Error: undefined variable or function "var"` + [red]`Eval Error: undefined variable or function "var"`
`>>>` [blue]`var ?= (1+2)` + `>>>` [blue]`var ?= (1+2)` +
[green]`3` + [green]`3`
`>>>` [blue]`var` +
`>>>` [blue]`var`
[green]`3` [green]`3`
NOTE: These operators have a high priority, in particular higher than the operator [blue]`=`. NOTE: These operators have a high priority, in particular higher than the operator [blue]`=`.
@@ -576,57 +690,87 @@ The table below shows all supported operators by decreasing priorities.
|=== |===
| Priority | Operators | Position | Operation | Operands and results | Priority | Operators | Position | Operation | Operands and results
.2+|*ITEM*| [blue]`.` | _Infix_ | _List item_| _list_ `"."` _integer_ -> _any_ .2+|*ITEM*| [blue]`[`...`]` | _Postfix_ | _List item_| _list_ `[` _integer_ `]` -> _any_
| [blue]`.` | _Infix_ | _Dict item_ | _dict_ `"."` _any_ -> _any_ | [blue]`[`...`]` | _Postfix_ | _Dict item_ | _dict_ `[` _any_ `]` -> _any_
.2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `"++"` -> _integer_ .2+|*INC*| [blue]`++` | _Postfix_ | _Post increment_| _integer-variable_ `++` -> _integer_
| [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `"++"` -> _any_ | [blue]`++` | _Postfix_ | _Next item_ | _iterator_ `++` -> _any_
.1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `"!"` -> _integer_ .2+|*DEFAULT*| [blue]`??` | _Infix_ | _Default value_| _variable_ `??` _any-expr_ -> _any_
.3+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| (`"+"`\|`"-"`) _number_ -> _number_ | [blue]`?=` | _Infix_ | _Default/assign value_| _variable_ `?=` _any-expr_ -> _any_
| [blue]`#` | _Prefix_ | _Lenght-of_ | `"#"` _collection_ -> _integer_ .1+| *ITER*^1^| [blue]`()` | _Prefix_ | _Iterator value_ | `()` _iterator_ -> _any_
| [blue]`#` | _Prefix_ | _Size-of_ | `"#"` _iterator_ -> _integer_ .1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ `!` -> _integer_
.5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ `"*"` _number_ -> _number_ .3+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| (`+`\|`-`) _number_ -> _number_
| [blue]`*` | _Infix_ | _String-repeat_ | _string_ `"*"` _integer_ -> _string_ | [blue]`#` | _Prefix_ | _Lenght-of_ | `#` _collection_ -> _integer_
| [blue]`/` | _Infix_ | _Division_ | _number_ `"/"` _number_ -> _number_ | [blue]`#` | _Prefix_ | _Size-of_ | `#` _iterator_ -> _integer_
| [blue]`./` | _Infix_ | _Float-division_ | __number__ `"./"` _number_ -> _float_ .2+|*SELECT*| [blue]`? : ::` | _Multi-Infix_ | _Case-Selector_ | _any-expr_ `?` _case-list_ _case-expr_ `:` _case-list_ _case-expr_ ... `::` _default-expr_ -> _any_
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `"%"` _integer_ -> _integer_ | [blue]`? : ::` | _Multi-Infix_ | _Index-Selector_ | _int-expr_ `?` _case-expr_ `:` _case-expr_ ... `::` _default-expr_ -> _any_
.6+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `"+"` _number_ -> _number_ .1+|*FRACT*| [blue]`\|` | _Infix_ | _Fraction_ | _integer_ `\|` _integer_ -> _fraction_
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `"+"` (_string_\|_number_) -> _string_ .5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ `*` _number_ -> _number_
| [blue]`+` | _Infix_ | _List-join_ | _list_ `"+"` _list_ -> _list_ | [blue]`*` | _Infix_ | _String-repeat_ | _string_ `*` _integer_ -> _string_
| [blue]`+` | _Infix_ | _Dict-join_ | _dict_ `"+"` _dict_ -> _dict_ | [blue]`/` | _Infix_ | _Division_ | _number_ `/` _number_ -> _number_
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ `"-"` _number_ -> _number_ | [blue]`./` | _Infix_ | _Float-division_ | __number__ `./` _number_ -> _float_
| [blue]`-` | _Infix_ | _List-difference_ | _list_ `"-"` _list_ -> _list_ | [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ `%` _integer_ -> _integer_
.8+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ `"<"` _comparable_ -> _boolean_ .6+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ `+` _number_ -> _number_
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `"\<="` _comparable_ -> _boolean_ | [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) `+` (_string_\|_number_) -> _string_
| [blue]`>` | _Infix_ | _greater_ | _comparable_ `">"` _comparable_ -> _boolean_ | [blue]`+` | _Infix_ | _List-join_ | _list_ `+` _list_ -> _list_
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ `">="` _comparable_ -> _boolean_ | [blue]`+` | _Infix_ | _Dict-join_ | _dict_ `+` _dict_ -> _dict_
| [blue]`==` | _Infix_ | _equal_ | _comparable_ `"=="` _comparable_ -> _boolean_ | [blue]`-` | _Infix_ | _Subtraction_ | _number_ `-` _number_ -> _number_
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ `"!="` _comparable_ -> _boolean_ | [blue]`-` | _Infix_ | _List-difference_ | _list_ `-` _list_ -> _list_
| [blue]`in` | _Infix_ | _member-of-list_ | _any_ `"in"` _list_ -> _boolean_ .8+|*RELATION*| [blue]`<` | _Infix_ | _Less_ | _comparable_ `<` _comparable_ -> _boolean_
| [blue]`in` | _Infix_ | _key-of-dict_ | _any_ `"in"` _dict_ -> _boolean_ | [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ `\<=` _comparable_ -> _boolean_
.1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | `"not"` _boolean_ -> _boolean_ | [blue]`>` | _Infix_ | _Greater_ | _comparable_ `>` _comparable_ -> _boolean_
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ `"and"` _boolean_ -> _boolean_ | [blue]`>=` | _Infix_ | _Greater-equal_ | _comparable_ `>=` _comparable_ -> _boolean_
| [blue]`&&` | _Infix_ | _and_ | _boolean_ `"&&"` _boolean_ -> _boolean_ | [blue]`==` | _Infix_ | _Equal_ | _comparable_ `==` _comparable_ -> _boolean_
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ `"or"` _boolean_ -> _boolean_ | [blue]`!=` | _Infix_ | _Not-equal_ | _comparable_ `!=` _comparable_ -> _boolean_
| [blue]`\|\|` | _Infix_ | _or_ | _boolean_ `"\|\|"` _boolean_ -> _boolean_ | [blue]`in` | _Infix_ | _Member-of-list_ | _any_ `in` _list_ -> _boolean_
.3+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_ | [blue]`in` | _Infix_ | _Key-of-dict_ | _any_ `in` _dict_ -> _boolean_
| [blue]`>>` | _Infix_ | _front-insert_ | _any_ ">>" _list_ -> _list_ .1+|*NOT*| [blue]`not` | _Prefix_ | _Not_ | `not` _boolean_ -> _boolean_
| [blue]`<<` | _Infix_ | _back-insert_ | _list_ "<<" _any_ -> _list_ .2+|*AND*| [blue]`and` | _Infix_ | _And_ | _boolean_ `and` _boolean_ -> _boolean_
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_ | [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
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. Actually, _Expr_ supports two types of function, _expr-functions_ and _go-functions_.
* _expr-functions_ are defined using _Expr_'s syntax. They can be passed as arguments to other functions and can be returned from functions. Moreover, they bind themselves to the defining context, thus becoming closures. * _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 _import_ the module in which they are defined.
In _Expr_ functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator [blue]`@` it is possibile to export local definition to the calling context.
=== _Expr_ function definition
A function is identified and referenced by its name. It can have zero or more parameter. _Expr_ functions also support optional parameters.
. Expr's function definition syntax
====
*_function-definition_* = _identifier_ "**=**" "**func(**" [_param-list_] "**)**" "**{**" _multi-expression_ "**}**"
_param_list_ = _required-param-list_ [ "**,**" _optional-param-list_ ]
_required-param-list_ = _identifier_ { "**,**" _identifier_ }
_optional-param-list_ = _optional-parm_ { "**,**" _optional-param_ }
_optional-param_ = _identifier_ "**=**" _any-expr_
====
.Examples
#TODO#
=== _Golang_ function definition
Description of how to define Golan functions and how to bind them to _Expr_ are topics treated in another document that I'll write, one day, maybe.
=== Function calls === Function calls
#TODO: function calls operations# #TODO: function calls operations#
=== Function definitions Functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator [blue]`@` it is possibile to export local definition to the calling context.
#TODO: function definitions operations#
== Iterators
#TODO: function calls operations#
== Builtins == Builtins
#TODO: builtins# #TODO: builtins#
@@ -637,4 +781,7 @@ In _Expr_ functions compute values in a local context (scope) that do not make e
[blue]_import([grey]#<source-file>#)_ loads the multi-expression contained in the specified source and returns its value. [blue]_import([grey]#<source-file>#)_ loads the multi-expression contained in the specified source and returns its value.
== Plugins
#TODO: plugins#
+528 -212
View File
File diff suppressed because one or more lines are too long
+12 -31
View File
@@ -1,49 +1,30 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// context.go // expr-context.go
package expr 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
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 // ----Expression Context
type ExprContext interface { type ExprContext interface {
Clone() ExprContext Clone() ExprContext
Merge(ctx ExprContext) // Merge(ctx ExprContext)
SetParent(ctx ExprContext)
GetParent() (ctx ExprContext)
GetVar(varName string) (value any, exists bool) GetVar(varName string) (value any, exists bool)
GetLast() any
SetVar(varName string, value any) SetVar(varName string, value any)
UnsafeSetVar(varName string, value any) UnsafeSetVar(varName string, value any)
EnumVars(func(name string) (accept bool)) (varNames []string) EnumVars(func(name string) (accept bool)) (varNames []string)
VarCount() int
DeleteVar(varName string)
EnumFuncs(func(name string) (accept bool)) (funcNames []string) EnumFuncs(func(name string) (accept bool)) (funcNames []string)
FuncCount() int
DeleteFunc(funcName string)
GetFuncInfo(name string) (item ExprFunc, exists bool) GetFuncInfo(name string) (item ExprFunc, exists bool)
Call(name string, args []any) (result any, err error) Call(name string, args []any) (result any, err error)
// RegisterFunc(name string, f Functor, minArgs, maxArgs int)
RegisterFuncInfo(info ExprFunc) RegisterFuncInfo(info ExprFunc)
RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) error RegisterFunc(name string, f Functor, returnType string, param []ExprFuncParam) error
} }
+38
View File
@@ -0,0 +1,38 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// expr-function.go
package expr
// ---- Functor interface
type Functor interface {
Typer
Invoke(ctx ExprContext, name string, args []any) (result any, err error)
SetFunc(info ExprFunc)
GetFunc() ExprFunc
GetParams() []ExprFuncParam
GetDefinitionContext() ExprContext
}
// ---- Function Param Info
type ExprFuncParam interface {
Name() string
Type() string
IsDefault() bool
IsOptional() bool
IsRepeat() bool
DefaultValue() any
}
// ---- Function Info
type ExprFunc interface {
Formatter
Name() string
MinArgs() int
MaxArgs() int
Functor() Functor
Params() []ExprFuncParam
ReturnType() string
PrepareCall(parentCtx ExprContext, name string, varParams *[]any) (ctx ExprContext, err error)
AllocContext(parentCtx ExprContext) (ctx ExprContext)
}
+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
}
-33
View File
@@ -1,33 +0,0 @@
builtin ["os.file", "base"];
readInt=func(fh){
line=readFile(fh);
line ? [nil] {nil} :: {int(line)}
};
ds={
"init":func(filename){
fh=openFile(filename);
fh ? [nil] {nil} :: { @current=readInt(fh); @prev=@current };
fh
},
"current":func(){
prev
},
"next":func(fh){
current ?
[nil] {current}
:: {@prev=current; @current=readInt(fh) but current}
},
"clean":func(fh){
closeFile(fh)
}
}
//;f=$(ds, "int.list")
/*
;f++
;f++
;f++
*/
//;add(f)
+14 -2
View File
@@ -6,7 +6,7 @@ package expr
import "fmt" import "fmt"
type FmtOpt uint16 type FmtOpt uint32 // lower 16 bits hold a bit-mask, higher 16 bits hold an indentation number
const ( const (
TTY FmtOpt = 1 << iota TTY FmtOpt = 1 << iota
@@ -30,6 +30,18 @@ func TruncateString(s string) (trunc string) {
return return
} }
func MakeFormatOptions(flags FmtOpt, indent int) FmtOpt {
return FmtOpt(indent<<16) | flags
}
func GetFormatFlags(opt FmtOpt) FmtOpt {
return opt & 0xFFFF
}
func GetFormatIndent(opt FmtOpt) int {
return int(opt >> 16)
}
type Formatter interface { type Formatter interface {
ToString(options FmtOpt) string ToString(options FmtOpt) string
} }
@@ -51,7 +63,7 @@ type Typer interface {
TypeName() string TypeName() string
} }
func typeName(v any) (name string) { func TypeName(v any) (name string) {
if v == nil { if v == nil {
name = "nil" name = "nil"
} else if typer, ok := v.(Typer); ok { } else if typer, ok := v.(Typer); ok {
+8 -8
View File
@@ -50,9 +50,10 @@ func makeGeneratingFraction(s string) (f *FractionType, err error) {
} else if s[0] == '+' { } else if s[0] == '+' {
s = s[1:] s = s[1:]
} }
if strings.HasSuffix(s, "()") { // if strings.HasSuffix(s, "()") {
s = s[0 : len(s)-2] // s = s[0 : len(s)-2]
} // }
s = strings.TrimSuffix(s, "()")
parts = strings.SplitN(s, ".", 2) parts = strings.SplitN(s, ".", 2)
if num, err = strconv.ParseInt(parts[0], 10, 64); err != nil { if num, err = strconv.ParseInt(parts[0], 10, 64); err != nil {
return return
@@ -70,7 +71,7 @@ func makeGeneratingFraction(s string) (f *FractionType, err error) {
} }
for _, c := range dec[0:lsd] { for _, c := range dec[0:lsd] {
if c < '0' || c > '9' { if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c) return nil, ErrExpectedGot("fract", "digit", c)
} }
num = num*10 + int64(c-'0') num = num*10 + int64(c-'0')
den = den * 10 den = den * 10
@@ -81,7 +82,7 @@ func makeGeneratingFraction(s string) (f *FractionType, err error) {
mul := int64(1) mul := int64(1)
for _, c := range subParts[0] { for _, c := range subParts[0] {
if c < '0' || c > '9' { if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c) return nil, ErrExpectedGot("fract", "digit", c)
} }
num = num*10 + int64(c-'0') num = num*10 + int64(c-'0')
sub = sub*10 + int64(c-'0') sub = sub*10 + int64(c-'0')
@@ -94,7 +95,7 @@ func makeGeneratingFraction(s string) (f *FractionType, err error) {
p := subParts[1][0 : len(subParts[1])-1] p := subParts[1][0 : len(subParts[1])-1]
for _, c := range p { for _, c := range p {
if c < '0' || c > '9' { if c < '0' || c > '9' {
return nil, errExpectedGot("fract", "digit", c) return nil, ErrExpectedGot("fract", "digit", c)
} }
num = num*10 + int64(c-'0') num = num*10 + int64(c-'0')
den = den*10 + 9 den = den*10 + 9
@@ -212,7 +213,7 @@ func anyToFract(v any) (f *FractionType, err error) {
} }
} }
if f == nil { if f == nil {
err = errExpectedGot("fract", typeFraction, v) err = ErrExpectedGot("fract", TypeFraction, v)
} }
return return
} }
@@ -311,7 +312,6 @@ func divAnyFract(af1, af2 any) (quot any, err error) {
if f2.num == 0 { if f2.num == 0 {
err = errors.New("division by zero") err = errors.New("division by zero")
return return
return
} }
if f1.num == 0 || f2.den == 0 { if f1.num == 0 || f2.den == 0 {
quot = 0 quot = 0
-36
View File
@@ -1,36 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-fmt.go
package expr
import "fmt"
func printFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var n int
if n, err = fmt.Print(args...); err == nil {
result = int64(n)
}
return
}
func printLnFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var n int
if n, err = fmt.Println(args...); err == nil {
result = int64(n)
}
return
}
func ImportFmtFuncs(ctx ExprContext) {
ctx.RegisterFunc("print", newGolangFunctor(printFunc), typeInt, []ExprFuncParam{
newFuncParamFlag(paramItem, pfRepeat),
})
ctx.RegisterFunc("println", newGolangFunctor(printLnFunc), typeInt, []ExprFuncParam{
newFuncParamFlag(paramItem, pfRepeat),
})
}
func init() {
registerImport("fmt", ImportFmtFuncs, "String and console formatting functions")
}
-150
View File
@@ -1,150 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-import.go
package expr
import (
"errors"
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
)
const ENV_EXPR_PATH = "EXPR_PATH"
func importFunc(ctx ExprContext, name string, args []any) (result any, err error) {
return importGeneral(ctx, name, args)
}
func importAllFunc(ctx ExprContext, name string, args []any) (result any, err error) {
enable(ctx, control_export_all)
return importGeneral(ctx, name, args)
}
func importGeneral(ctx ExprContext, name string, args []any) (result any, err error) {
var dirList []string
dirList = addEnvImportDirs(dirList)
dirList = addPresetImportDirs(ctx, dirList)
result, err = doImport(ctx, name, dirList, NewArrayIterator(args))
return
}
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(IsString(paramValue) /*|| isList(paramValue)*/) {
err = fmt.Errorf("%s(): param nr %d has wrong type %T, string expected", funcName, paramPos+1, paramValue)
}
return
}
func addEnvImportDirs(dirList []string) []string {
if dirSpec, exists := os.LookupEnv(ENV_EXPR_PATH); exists {
dirs := strings.Split(dirSpec, ":")
if dirList == nil {
dirList = dirs
} else {
dirList = append(dirList, dirs...)
}
}
return dirList
}
func addPresetImportDirs(ctx ExprContext, dirList []string) []string {
if dirSpec, exists := getControlString(ctx, ControlImportPath); exists {
dirs := strings.Split(dirSpec, ":")
if dirList == nil {
dirList = dirs
} else {
dirList = append(dirList, dirs...)
}
}
return dirList
}
func isFile(filePath string) bool {
info, err := os.Stat(filePath)
return (err == nil || errors.Is(err, os.ErrExist)) && info.Mode().IsRegular()
}
func searchAmongPath(filename string, dirList []string) (filePath string) {
for _, dir := range dirList {
if fullPath := path.Join(dir, filename); isFile(fullPath) {
filePath = fullPath
break
}
}
return
}
func isPathRelative(filePath string) bool {
unixPath := filepath.ToSlash(filePath)
return strings.HasPrefix(unixPath, "./") || strings.HasPrefix(unixPath, "../")
}
func makeFilepath(filename string, dirList []string) (filePath string, err error) {
if path.IsAbs(filename) || isPathRelative(filename) {
if isFile(filename) {
filePath = filename
}
} else {
filePath = searchAmongPath(filename, dirList)
}
if len(filePath) == 0 {
err = fmt.Errorf("source file %q not found", filename)
}
return
}
func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (result any, err error) {
var v any
var sourceFilepath string
for v, err = it.Next(); err == nil; v, err = it.Next() {
if err = checkStringParamExpected(name, v, it.Index()); err != nil {
break
}
if sourceFilepath, err = makeFilepath(v.(string), dirList); err != nil {
break
}
var file *os.File
if file, err = os.Open(sourceFilepath); err == nil {
defer file.Close()
var expr *ast
scanner := NewScanner(file, DefaultTranslations())
parser := NewParser(ctx)
if expr, err = parser.parseGeneral(scanner, true, true); err == nil {
result, err = expr.eval(ctx, false)
}
if err != nil {
break
}
} else {
break
}
}
if err != nil {
if err == io.EOF {
err = nil
} else {
result = nil
}
}
return
}
func ImportImportFuncs(ctx ExprContext) {
ctx.RegisterFunc("import", newGolangFunctor(importFunc), typeAny, []ExprFuncParam{
newFuncParamFlag(paramFilepath, pfRepeat),
})
ctx.RegisterFunc("importAll", newGolangFunctor(importAllFunc), typeAny, []ExprFuncParam{
newFuncParamFlag(paramFilepath, pfRepeat),
})
}
func init() {
registerImport("import", ImportImportFuncs, "Functions import() and include()")
}
+100 -93
View File
@@ -21,7 +21,7 @@ func (functor *baseFunctor) ToString(opt FmtOpt) (s string) {
if functor.info != nil { if functor.info != nil {
s = functor.info.ToString(opt) s = functor.info.ToString(opt)
} else { } else {
s = "func() {<body>}" s = "func(){}"
} }
return s return s
} }
@@ -42,65 +42,17 @@ func (functor *baseFunctor) GetFunc() ExprFunc {
return functor.info return functor.info
} }
// ---- Linking with Go functions func (functor *baseFunctor) GetDefinitionContext() ExprContext {
type golangFunctor struct { return nil
baseFunctor
f FuncTemplate
}
func newGolangFunctor(f FuncTemplate) *golangFunctor {
return &golangFunctor{f: f}
}
func (functor *golangFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
return functor.f(ctx, name, args)
}
// ---- Linking with Expr functions
type exprFunctor struct {
baseFunctor
params []ExprFuncParam
expr Expr
defCtx ExprContext
}
// func newExprFunctor(e Expr, params []string, ctx ExprContext) *exprFunctor {
// return &exprFunctor{expr: e, params: params, defCtx: ctx}
// }
func newExprFunctor(e Expr, params []ExprFuncParam, ctx ExprContext) *exprFunctor {
return &exprFunctor{expr: e, params: params, defCtx: ctx}
}
func (functor *exprFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
if functor.defCtx != nil {
ctx.Merge(functor.defCtx)
}
for i, p := range functor.params {
if i < len(args) {
arg := args[i]
if funcArg, ok := arg.(Functor); ok {
// ctx.RegisterFunc(p, functor, 0, -1)
paramSpecs := funcArg.GetParams()
ctx.RegisterFunc(p.Name(), funcArg, typeAny, paramSpecs)
} else {
ctx.UnsafeSetVar(p.Name(), arg)
}
} else {
ctx.UnsafeSetVar(p.Name(), nil)
}
}
result, err = functor.expr.eval(ctx, false)
return
} }
// ---- Function Parameters // ---- Function Parameters
type paramFlags uint16 type paramFlags uint16
const ( const (
pfOptional paramFlags = 1 << iota PfDefault paramFlags = 1 << iota
pfRepeat PfOptional
PfRepeat
) )
type funcParamInfo struct { type funcParamInfo struct {
@@ -109,15 +61,15 @@ type funcParamInfo struct {
defaultValue any defaultValue any
} }
func newFuncParam(name string) ExprFuncParam { func NewFuncParam(name string) ExprFuncParam {
return &funcParamInfo{name: name} return &funcParamInfo{name: name}
} }
func newFuncParamFlag(name string, flags paramFlags) ExprFuncParam { func NewFuncParamFlag(name string, flags paramFlags) ExprFuncParam {
return &funcParamInfo{name: name, flags: flags} return &funcParamInfo{name: name, flags: flags}
} }
func newFuncParamFlagDef(name string, flags paramFlags, defValue any) *funcParamInfo { func NewFuncParamFlagDef(name string, flags paramFlags, defValue any) *funcParamInfo {
return &funcParamInfo{name: name, flags: flags, defaultValue: defValue} return &funcParamInfo{name: name, flags: flags, defaultValue: defValue}
} }
@@ -126,15 +78,19 @@ func (param *funcParamInfo) Name() string {
} }
func (param *funcParamInfo) Type() string { func (param *funcParamInfo) Type() string {
return "any" return TypeAny
}
func (param *funcParamInfo) IsDefault() bool {
return (param.flags & PfDefault) != 0
} }
func (param *funcParamInfo) IsOptional() bool { func (param *funcParamInfo) IsOptional() bool {
return (param.flags & pfOptional) != 0 return (param.flags & PfOptional) != 0
} }
func (param *funcParamInfo) IsRepeat() bool { func (param *funcParamInfo) IsRepeat() bool {
return (param.flags & pfRepeat) != 0 return (param.flags & PfRepeat) != 0
} }
func (param *funcParamInfo) DefaultValue() any { func (param *funcParamInfo) DefaultValue() any {
@@ -142,50 +98,51 @@ func (param *funcParamInfo) DefaultValue() any {
} }
// --- Functions // --- Functions
// funcInfo implements ExprFunc
type funcInfo struct { type funcInfo struct {
name string name string
minArgs int minArgs int
maxArgs int maxArgs int
functor Functor functor Functor
params []ExprFuncParam formalParams []ExprFuncParam
returnType string returnType string
} }
func newFuncInfo(name string, functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) { func newFuncInfo(name string, functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
var minArgs = 0 var minArgs = 0
var maxArgs = 0 var maxArgs = 0
if params != nil { for _, p := range params {
for _, p := range params { if maxArgs == -1 {
if maxArgs == -1 { return nil, fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name())
return nil, fmt.Errorf("no more params can be specified after the ellipsis symbol: %q", p.Name()) }
} if p.IsDefault() || p.IsOptional() {
if p.IsOptional() { maxArgs++
maxArgs++ } else if maxArgs == minArgs {
} else if maxArgs == minArgs { minArgs++
minArgs++ maxArgs++
maxArgs++ } else {
} else { return nil, fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name())
return nil, fmt.Errorf("can't specify non-optional param after optional ones: %q", p.Name()) }
} if p.IsRepeat() {
if p.IsRepeat() { minArgs--
minArgs-- maxArgs = -1
maxArgs = -1
}
} }
} }
info = &funcInfo{ 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) functor.SetFunc(info)
return info, nil return info, nil
} }
func newUnnamedFuncInfo(functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) { // func newUnnamedFuncInfo(functor Functor, returnType string, params []ExprFuncParam) (info *funcInfo, err error) {
return newFuncInfo("unnamed", functor, returnType, params) // return newFuncInfo("unnamed", functor, returnType, params)
} // }
func (info *funcInfo) Params() []ExprFuncParam { func (info *funcInfo) Params() []ExprFuncParam {
return info.params return info.formalParams
} }
func (info *funcInfo) ReturnType() string { func (info *funcInfo) ReturnType() string {
@@ -200,14 +157,14 @@ func (info *funcInfo) ToString(opt FmtOpt) string {
sb.WriteString(info.Name()) sb.WriteString(info.Name())
} }
sb.WriteByte('(') sb.WriteByte('(')
if info.params != nil { if info.formalParams != nil {
for i, p := range info.params { for i, p := range info.formalParams {
if i > 0 { if i > 0 {
sb.WriteString(", ") sb.WriteString(", ")
} }
sb.WriteString(p.Name()) sb.WriteString(p.Name())
if p.IsOptional() { if p.IsDefault() {
sb.WriteByte('=') sb.WriteByte('=')
if s, ok := p.DefaultValue().(string); ok { if s, ok := p.DefaultValue().(string); ok {
sb.WriteByte('"') sb.WriteByte('"')
@@ -226,9 +183,9 @@ func (info *funcInfo) ToString(opt FmtOpt) string {
if len(info.returnType) > 0 { if len(info.returnType) > 0 {
sb.WriteString(info.returnType) sb.WriteString(info.returnType)
} else { } else {
sb.WriteString(typeAny) sb.WriteString(TypeAny)
} }
sb.WriteString(" {<body>}") sb.WriteString("{}")
return sb.String() return sb.String()
} }
@@ -247,3 +204,53 @@ func (info *funcInfo) MaxArgs() int {
func (info *funcInfo) Functor() Functor { func (info *funcInfo) Functor() Functor {
return info.functor return info.functor
} }
func (info *funcInfo) AllocContext(parentCtx ExprContext) (ctx ExprContext) {
if defCtx := info.functor.GetDefinitionContext(); defCtx != nil {
ctx = defCtx.Clone()
ctx.SetParent(defCtx)
} else {
ctx = parentCtx.Clone()
ctx.SetParent(parentCtx)
}
return
}
func (info *funcInfo) PrepareCall(parentCtx ExprContext, name string, varActualParams *[]any) (ctx ExprContext, err error) {
passedCount := len(*varActualParams)
if info.MinArgs() > passedCount {
err = ErrTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
}
for i := passedCount; i < len(info.formalParams); i++ {
p := info.formalParams[i]
if !p.IsDefault() {
break
}
*varActualParams = append(*varActualParams, p.DefaultValue())
}
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(*varActualParams) {
err = ErrTooMuchParams(name, info.MaxArgs(), len(*varActualParams))
}
if err == nil {
ctx = info.AllocContext(parentCtx)
}
return
}
// ----- Call a function ---
func CallFunction(parentCtx ExprContext, name string, actualParams []any) (result any, err error) {
if info, exists, _ := GetFuncInfo(parentCtx, name); exists {
var ctx ExprContext
if ctx, err = info.PrepareCall(parentCtx, name, &actualParams); err == nil {
functor := info.Functor()
result, err = functor.Invoke(ctx, name, actualParams)
exportObjectsToParent(ctx)
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
+78 -4
View File
@@ -4,13 +4,16 @@
// global-context.go // global-context.go
package expr package expr
import "path/filepath" import (
"path/filepath"
"strings"
)
var globalCtx *SimpleStore var globalCtx *SimpleStore
func ImportInContext(name string) (exists bool) { func ImportInContext(name string) (exists bool) {
var mod *module var mod *builtinModule
if mod, exists = moduleRegister[name]; exists { if mod, exists = builtinModuleRegister[name]; exists {
mod.importFunc(globalCtx) mod.importFunc(globalCtx)
mod.imported = true mod.imported = true
} }
@@ -19,7 +22,7 @@ func ImportInContext(name string) (exists bool) {
func ImportInContextByGlobPattern(pattern string) (count int, err error) { func ImportInContextByGlobPattern(pattern string) (count int, err error) {
var matched bool var matched bool
for name, mod := range moduleRegister { for name, mod := range builtinModuleRegister {
if matched, err = filepath.Match(pattern, name); err == nil { if matched, err = filepath.Match(pattern, name); err == nil {
if matched { if matched {
count++ count++
@@ -63,7 +66,78 @@ func GetFuncInfo(ctx ExprContext, name string) (item ExprFunc, exists bool, owne
return return
} }
func GlobalCtrlSet(name string, newValue any) (currentValue any) {
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
currentValue, _ = globalCtx.GetVar(name)
globalCtx.SetVar(name, newValue)
return currentValue
}
func GlobalCtrlGet(name string) (currentValue any) {
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
currentValue, _ = globalCtx.GetVar(name)
return currentValue
}
func CtrlEnable(ctx ExprContext, name string) (currentStatus bool) {
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
if v, exists := ctx.GetVar(name); exists && IsBool(v) {
currentStatus, _ = v.(bool)
}
ctx.SetVar(name, true)
return currentStatus
}
func CtrlDisable(ctx ExprContext, name string) (currentStatus bool) {
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
if v, exists := ctx.GetVar(name); exists && IsBool(v) {
currentStatus, _ = v.(bool)
}
ctx.SetVar(name, false)
return currentStatus
}
func CtrlIsEnabled(ctx ExprContext, name string) (status bool) {
var v any
var exists bool
if !strings.HasPrefix(name, "_") {
name = "_" + name
}
if v, exists = ctx.GetVar(name); !exists {
v, exists = globalCtx.GetVar(name)
}
if exists {
if b, ok := v.(bool); ok {
status = b
}
}
return
}
func getControlString(name string) (s string, exists bool) {
var v any
if v, exists = globalCtx.GetVar(name); exists {
s, exists = v.(string)
}
return
}
func init() { func init() {
globalCtx = NewSimpleStore() globalCtx = NewSimpleStore()
initDefaultVars(globalCtx)
ImportBuiltinsFuncs(globalCtx) ImportBuiltinsFuncs(globalCtx)
} }
+6 -6
View File
@@ -16,10 +16,10 @@ func EvalString(ctx ExprContext, source string) (result any, err error) {
r := strings.NewReader(source) r := strings.NewReader(source)
scanner := NewScanner(r, DefaultTranslations()) scanner := NewScanner(r, DefaultTranslations())
parser := NewParser(ctx) parser := NewParser()
if tree, err = parser.Parse(scanner); err == nil { if tree, err = parser.Parse(scanner); err == nil {
result, err = tree.eval(ctx, true) result, err = tree.Eval(ctx)
} }
return return
} }
@@ -38,10 +38,10 @@ func EvalStringV(source string, args []Arg) (result any, err error) {
for _, arg := range args { for _, arg := range args {
if isFunc(arg.Value) { if isFunc(arg.Value) {
if f, ok := arg.Value.(FuncTemplate); ok { if f, ok := arg.Value.(FuncTemplate); ok {
functor := newGolangFunctor(f) functor := NewGolangFunctor(f)
// ctx.RegisterFunc(arg.Name, functor, 0, -1) // ctx.RegisterFunc(arg.Name, functor, 0, -1)
ctx.RegisterFunc(arg.Name, functor, typeAny, []ExprFuncParam{ ctx.RegisterFunc(arg.Name, functor, TypeAny, []ExprFuncParam{
newFuncParamFlagDef(paramValue, pfOptional|pfRepeat, 0), NewFuncParamFlagDef(ParamValue, PfDefault|PfRepeat, 0),
}) })
} else { } else {
err = fmt.Errorf("invalid function specification: %q", arg.Name) err = fmt.Errorf("invalid function specification: %q", arg.Name)
@@ -68,7 +68,7 @@ func EvalStringV(source string, args []Arg) (result any, err error) {
func EvalStream(ctx ExprContext, r io.Reader) (result any, err error) { func EvalStream(ctx ExprContext, r io.Reader) (result any, err error) {
var tree *ast var tree *ast
scanner := NewScanner(r, DefaultTranslations()) scanner := NewScanner(r, DefaultTranslations())
parser := NewParser(ctx) parser := NewParser()
if tree, err = parser.Parse(scanner); err == nil { if tree, err = parser.Parse(scanner); err == nil {
result, err = tree.Eval(ctx) result, err = tree.Eval(ctx)
+112
View File
@@ -0,0 +1,112 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// import-utils.go
package expr
import (
"errors"
"fmt"
"os"
"path"
"path/filepath"
"strings"
)
const (
ENV_EXPR_SOURCE_PATH = "EXPR_PATH"
ENV_EXPR_PLUGIN_PATH = "EXPR_PLUGIN_PATH"
)
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(IsString(paramValue) /*|| isList(paramValue)*/) {
err = fmt.Errorf("%s(): param nr %d has wrong type %s, string expected", funcName, paramPos+1, TypeName(paramValue))
}
return
}
// func addSourceEnvImportDirs(varName string, dirList []string) []string {
// return addEnvImportDirs(ENV_EXPR_SOURCE_PATH, dirList)
// }
// func addPluginEnvImportDirs(varName string, dirList []string) []string {
// return addEnvImportDirs(ENV_EXPR_PLUGIN_PATH, dirList)
// }
func addEnvImportDirs(envVarName string, dirList []string) []string {
if dirSpec, exists := os.LookupEnv(envVarName); exists {
dirs := strings.Split(dirSpec, ":")
if dirList == nil {
dirList = dirs
} else {
dirList = append(dirList, dirs...)
}
}
return dirList
}
func addSearchDirs(endingPath string, dirList []string) []string {
if dirSpec, exists := getControlString(ControlSearchPath); exists {
dirs := strings.Split(dirSpec, ":")
if dirList == nil {
dirList = dirs
} else {
if len(endingPath) > 0 {
for _, d := range dirs {
dirList = append(dirList, path.Join(d, endingPath))
}
} else {
dirList = append(dirList, dirs...)
}
}
}
return dirList
}
func buildSearchDirList(endingPath, envVarName string) (dirList []string) {
dirList = addEnvImportDirs(envVarName, dirList)
dirList = addSearchDirs(endingPath, dirList)
return
}
func isFile(filePath string) bool {
info, err := os.Stat(filePath)
return (err == nil || errors.Is(err, os.ErrExist)) && info.Mode().IsRegular()
}
func searchAmongPath(filename string, dirList []string) (filePath string) {
var err error
for _, dir := range dirList {
if dir, err = ExpandPath(dir); err != nil {
continue
}
if fullPath := path.Join(dir, filename); isFile(fullPath) {
filePath = fullPath
break
}
}
return
}
func isPathRelative(filePath string) bool {
unixPath := filepath.ToSlash(filePath)
return strings.HasPrefix(unixPath, "./") || strings.HasPrefix(unixPath, "../")
}
func makeFilepath(filename string, dirList []string) (filePath string, err error) {
if filename, err = ExpandPath(filename); err != nil {
return
}
if path.IsAbs(filename) || isPathRelative(filename) {
if isFile(filename) {
filePath = filename
}
} else {
filePath = searchAmongPath(filename, dirList)
}
if len(filePath) == 0 {
err = fmt.Errorf("file %q not found", filename)
}
return
}
+11 -7
View File
@@ -12,19 +12,23 @@ import (
// Operator names // Operator names
const ( const (
initName = "init" InitName = "init"
cleanName = "clean" CleanName = "clean"
resetName = "reset" ResetName = "reset"
nextName = "next" NextName = "next"
currentName = "current" CurrentName = "current"
indexName = "index" IndexName = "index"
countName = "count" CountName = "count"
FilterName = "filter"
MapName = "map"
) )
type Iterator interface { type Iterator interface {
Typer
Next() (item any, err error) // must return io.EOF after the last item Next() (item any, err error) // must return io.EOF after the last item
Current() (item any, err error) Current() (item any, err error)
Index() int Index() int
Count() int
} }
type ExtIterator interface { type ExtIterator interface {
+22 -9
View File
@@ -1,7 +1,7 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// iter-list.go // list-iterator.go
package expr package expr
import ( import (
@@ -26,21 +26,21 @@ func NewListIterator(list *ListType, args []any) (it *ListIterator) {
} }
it = &ListIterator{a: list, count: 0, index: -1, start: 0, stop: listLen - 1, step: 1} it = &ListIterator{a: list, count: 0, index: -1, start: 0, stop: listLen - 1, step: 1}
if argc >= 1 { if argc >= 1 {
if i, err := toInt(args[0], "start index"); err == nil { if i, err := ToGoInt(args[0], "start index"); err == nil {
if i < 0 { if i < 0 {
i = listLen + i i = listLen + i
} }
it.start = i it.start = i
} }
if argc >= 2 { if argc >= 2 {
if i, err := toInt(args[1], "stop index"); err == nil { if i, err := ToGoInt(args[1], "stop index"); err == nil {
if i < 0 { if i < 0 {
i = listLen + i i = listLen + i
} }
it.stop = i it.stop = i
} }
if argc >= 3 { if argc >= 3 {
if i, err := toInt(args[2], "step"); err == nil { if i, err := ToGoInt(args[2], "step"); err == nil {
if i < 0 { if i < 0 {
i = -i i = -i
} }
@@ -85,18 +85,26 @@ func (it *ListIterator) String() string {
return fmt.Sprintf("$(#%d)", l) return fmt.Sprintf("$(#%d)", l)
} }
func (it *ListIterator) TypeName() string {
return "ListIterator"
}
func (it *ListIterator) HasOperation(name string) bool { func (it *ListIterator) HasOperation(name string) bool {
yes := name == resetName || name == indexName || name == countName yes := name == NextName || name == ResetName || name == IndexName || name == CountName || name == CurrentName
return yes return yes
} }
func (it *ListIterator) CallOperation(name string, args []any) (v any, err error) { func (it *ListIterator) CallOperation(name string, args []any) (v any, err error) {
switch name { switch name {
case resetName: case NextName:
v, err = it.Next()
case ResetName:
v, err = it.Reset() v, err = it.Reset()
case indexName: case IndexName:
v = int64(it.Index()) v = int64(it.Index())
case countName: case CurrentName:
v, err = it.Current()
case CountName:
v = it.count v = it.count
default: default:
err = errNoOperation(name) err = errNoOperation(name)
@@ -135,7 +143,12 @@ func (it *ListIterator) Index() int {
return it.index return it.index
} }
func (it *ListIterator) Count() int {
return it.count
}
func (it *ListIterator) Reset() (bool, error) { func (it *ListIterator) Reset() (bool, error) {
it.index = it.start it.index = it.start - it.step
it.count = 0
return true, nil return true, nil
} }
+52 -19
View File
@@ -20,27 +20,57 @@ func newListA(listAny ...any) (list *ListType) {
} }
func newList(listAny []any) (list *ListType) { func newList(listAny []any) (list *ListType) {
return NewList(listAny)
}
func NewList(listAny []any) (list *ListType) {
if listAny != nil { if listAny != nil {
ls := make(ListType, len(listAny)) ls := make(ListType, len(listAny))
for i, item := range listAny { // for i, item := range listAny {
ls[i] = item // ls[i] = item
} // }
copy(ls, listAny)
list = &ls list = &ls
} }
return return
} }
func MakeList(length, capacity int) (list *ListType) {
if capacity < length {
capacity = length
}
ls := make(ListType, length, capacity)
list = &ls
return
}
func ListFromStrings(stringList []string) (list *ListType) {
list = MakeList(len(stringList), 0)
for i, s := range stringList {
(*list)[i] = s
}
return
}
func (ls *ListType) ToString(opt FmtOpt) (s string) { func (ls *ListType) ToString(opt FmtOpt) (s string) {
indent := GetFormatIndent(opt)
flags := GetFormatFlags(opt)
var sb strings.Builder var sb strings.Builder
sb.WriteByte('[') sb.WriteByte('[')
if len(*ls) > 0 { if len(*ls) > 0 {
if opt&MultiLine != 0 { innerOpt := MakeFormatOptions(flags, indent+1)
sb.WriteString("\n ") nest := strings.Repeat(" ", indent+1)
if flags&MultiLine != 0 {
sb.WriteByte('\n')
sb.WriteString(nest)
} }
for i, item := range []any(*ls) { for i, item := range []any(*ls) {
if i > 0 { if i > 0 {
if opt&MultiLine != 0 { if flags&MultiLine != 0 {
sb.WriteString(",\n ") sb.WriteString(",\n")
sb.WriteString(nest)
} else { } else {
sb.WriteString(", ") sb.WriteString(", ")
} }
@@ -49,17 +79,20 @@ func (ls *ListType) ToString(opt FmtOpt) (s string) {
sb.WriteByte('"') sb.WriteByte('"')
sb.WriteString(s) sb.WriteString(s)
sb.WriteByte('"') sb.WriteByte('"')
} else if formatter, ok := item.(Formatter); ok {
sb.WriteString(formatter.ToString(innerOpt))
} else { } else {
sb.WriteString(fmt.Sprintf("%v", item)) sb.WriteString(fmt.Sprintf("%v", item))
} }
} }
if opt&MultiLine != 0 { if flags&MultiLine != 0 {
sb.WriteByte('\n') sb.WriteByte('\n')
sb.WriteString(strings.Repeat(" ", indent))
} }
} }
sb.WriteByte(']') sb.WriteByte(']')
s = sb.String() s = sb.String()
if opt&Truncate != 0 && len(s) > TruncateSize { if flags&Truncate != 0 && len(s) > TruncateSize {
s = TruncateString(s) s = TruncateString(s)
} }
return return
@@ -73,16 +106,16 @@ func (ls *ListType) TypeName() string {
return "list" return "list"
} }
func (list *ListType) indexDeepCmp(target any) (index int) { // func (list *ListType) indexDeepCmp(target any) (index int) {
index = -1 // index = -1
for i, item := range *list { // for i, item := range *list {
if reflect.DeepEqual(item, target) { // if reflect.DeepEqual(item, target) {
index = i // index = i
break // break
} // }
} // }
return // return
} // }
func (ls *ListType) contains(t *ListType) (answer bool) { func (ls *ListType) contains(t *ListType) (answer bool) {
if len(*ls) >= len(*t) { if len(*ls) >= len(*t) {
-48
View File
@@ -1,48 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// module-register.go
package expr
import (
"fmt"
)
type module struct {
importFunc func(ExprContext)
description string
imported bool
}
func newModule(importFunc func(ExprContext), description string) *module {
return &module{importFunc, description, false}
}
var moduleRegister map[string]*module
func registerImport(name string, importFunc func(ExprContext), description string) {
if moduleRegister == nil {
moduleRegister = make(map[string]*module)
}
if _, exists := moduleRegister[name]; exists {
panic(fmt.Errorf("module %q already registered", name))
}
moduleRegister[name] = newModule(importFunc, description)
}
func IterateModules(op func(name, description string, imported bool) bool) {
if op != nil {
for name, mod := range moduleRegister {
if !op(name, mod.description, mod.imported) {
break
}
}
}
}
// ----
func init() {
if moduleRegister == nil {
moduleRegister = make(map[string]*module)
}
}
+2 -2
View File
@@ -18,8 +18,8 @@ func newDictTerm(args map[any]*term) *term {
} }
// -------- dict func // -------- dict func
func evalDict(ctx ExprContext, self *term) (v any, err error) { func evalDict(ctx ExprContext, opTerm *term) (v any, err error) {
dict, _ := self.value().(map[any]*term) dict, _ := opTerm.value().(map[any]*term)
items := make(DictType, len(dict)) items := make(DictType, len(dict))
for key, tree := range dict { for key, tree := range dict {
var param any var param any
+3 -3
View File
@@ -20,11 +20,11 @@ func newExprTerm(root *term) *term {
} }
// -------- eval expr // -------- eval expr
func evalExpr(ctx ExprContext, self *term) (v any, err error) { func evalExpr(ctx ExprContext, opTerm *term) (v any, err error) {
if expr, ok := self.value().(*term); ok { if expr, ok := opTerm.value().(*term); ok {
v, err = expr.compute(ctx) v, err = expr.compute(ctx)
} else { } else {
err = fmt.Errorf("expression expected, got %T", self.value()) err = fmt.Errorf("expression expected, got %T", opTerm.value())
} }
return return
} }
+11 -43
View File
@@ -6,7 +6,6 @@ package expr
import ( import (
"errors" "errors"
"fmt"
) )
// -------- function call term // -------- function call term
@@ -22,37 +21,10 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
} }
// -------- eval func call // -------- eval func call
func checkFunctionCall(ctx ExprContext, name string, varParams *[]any) (err error) { func evalFuncCall(ctx ExprContext, opTerm *term) (v any, err error) {
if info, exists, owner := GetFuncInfo(ctx, name); exists { name, _ := opTerm.tk.Value.(string)
passedCount := len(*varParams) params := make([]any, len(opTerm.children), len(opTerm.children)+5)
if info.MinArgs() > passedCount { for i, tree := range opTerm.children {
err = errTooFewParams(name, info.MinArgs(), info.MaxArgs(), passedCount)
}
for i, p := range info.Params() {
if i >= passedCount {
if !p.IsOptional() {
break
}
*varParams = append(*varParams, p.DefaultValue())
}
}
if err == nil && info.MaxArgs() >= 0 && info.MaxArgs() < len(*varParams) {
err = errTooMuchParams(name, info.MaxArgs(), len(*varParams))
}
if err == nil && owner != ctx {
ctx.RegisterFuncInfo(info)
}
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
ctx := cloneContext(parentCtx)
name, _ := self.tk.Value.(string)
params := make([]any, len(self.children), len(self.children)+5)
for i, tree := range self.children {
var param any var param any
if param, err = tree.compute(ctx); err != nil { if param, err = tree.compute(ctx); err != nil {
break break
@@ -61,11 +33,7 @@ func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
} }
if err == nil { if err == nil {
if err = checkFunctionCall(ctx, name, &params); err == nil { v, err = CallFunction(ctx, name, params)
if v, err = ctx.Call(name, params); err == nil {
exportObjects(parentCtx, ctx)
}
}
} }
return return
} }
@@ -83,20 +51,20 @@ func newFuncDefTerm(tk *Token, args []*term) *term {
} }
// -------- eval func def // -------- eval func def
func evalFuncDef(ctx ExprContext, self *term) (v any, err error) { func evalFuncDef(ctx ExprContext, opTerm *term) (v any, err error) {
bodySpec := self.value() bodySpec := opTerm.value()
if expr, ok := bodySpec.(*ast); ok { if expr, ok := bodySpec.(*ast); ok {
paramList := make([]ExprFuncParam, 0, len(self.children)) paramList := make([]ExprFuncParam, 0, len(opTerm.children))
for _, param := range self.children { for _, param := range opTerm.children {
var defValue any var defValue any
flags := paramFlags(0) flags := paramFlags(0)
if len(param.children) > 0 { if len(param.children) > 0 {
flags |= pfOptional flags |= PfDefault
if defValue, err = param.children[0].compute(ctx); err != nil { if defValue, err = param.children[0].compute(ctx); err != nil {
return return
} }
} }
info := newFuncParamFlagDef(param.source(), flags, defValue) info := NewFuncParamFlagDef(param.source(), flags, defValue)
paramList = append(paramList, info) paramList = append(paramList, info)
} }
v = newExprFunctor(expr, paramList, ctx) v = newExprFunctor(expr, paramList, ctx)
+40 -45
View File
@@ -5,28 +5,27 @@
package expr package expr
import ( import (
"fmt"
"slices" "slices"
"strings" "strings"
) )
// -------- iterator term // -------- iterator term
func newDsIteratorTerm(tk *Token, dsTerm *term, args []*term) *term { // func newDsIteratorTerm(tk *Token, dsTerm *term, args []*term) *term {
tk.Sym = SymIterator // tk.Sym = SymIterator
children := make([]*term, 0, 1+len(args)) // children := make([]*term, 0, 1+len(args))
children = append(children, dsTerm) // children = append(children, dsTerm)
children = append(children, args...) // children = append(children, args...)
return &term{ // return &term{
tk: *tk, // tk: *tk,
parent: nil, // parent: nil,
children: children, // children: children,
position: posLeaf, // position: posLeaf,
priority: priValue, // priority: priValue,
evalFunc: evalIterator, // evalFunc: evalIterator,
} // }
} // }
func newIteratorTerm(tk *Token, args []*term) *term { func newIteratorTerm(tk *Token, args []*term) *term {
tk.Sym = SymIterator tk.Sym = SymIterator
@@ -42,9 +41,9 @@ func newIteratorTerm(tk *Token, args []*term) *term {
// -------- eval iterator // -------- eval iterator
func evalTermArray(ctx ExprContext, a []*term) (values []any, err error) { func evalTermArray(ctx ExprContext, terms []*term) (values []any, err error) {
values = make([]any, len(a)) values = make([]any, len(terms))
for i, t := range a { for i, t := range terms {
var value any var value any
if value, err = t.compute(ctx); err == nil { if value, err = t.compute(ctx); err == nil {
values[i] = value values[i] = value
@@ -55,26 +54,24 @@ func evalTermArray(ctx ExprContext, a []*term) (values []any, err error) {
return return
} }
func evalFirstChild(ctx ExprContext, self *term) (value any, err error) { func evalFirstChild(ctx ExprContext, iteratorTerm *term) (value any, err error) {
if len(self.children) < 1 || self.children[0] == nil { if len(iteratorTerm.children) < 1 || iteratorTerm.children[0] == nil {
err = self.Errorf("missing the data-source parameter") err = iteratorTerm.Errorf("missing the data-source parameter")
return return
} }
value, err = self.children[0].compute(ctx) value, err = iteratorTerm.children[0].compute(ctx)
return return
} }
func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map[string]Functor, err error) { func getDataSourceDict(iteratorTerm *term, firstChildValue any) (ds map[string]Functor, err error) {
// if dictAny, ok := firstChildValue.(map[any]any); ok {
if dictAny, ok := firstChildValue.(*DictType); ok { if dictAny, ok := firstChildValue.(*DictType); ok {
requiredFields := []string{currentName, nextName} requiredFields := []string{NextName}
fieldsMask := 0b11 fieldsMask := 0b1
foundFields := 0 foundFields := 0
ds = make(map[string]Functor) ds = make(map[string]Functor)
for keyAny, item := range *dictAny { for keyAny, item := range *dictAny {
if key, ok := keyAny.(string); ok { if key, ok := keyAny.(string); ok {
//if functor, ok := item.(*funcDefFunctor); ok {
if functor, ok := item.(Functor); ok { if functor, ok := item.(Functor); ok {
ds[key] = functor ds[key] = functor
if index := slices.Index(requiredFields, key); index >= 0 { if index := slices.Index(requiredFields, key); index >= 0 {
@@ -91,57 +88,55 @@ func getDataSourceDict(ctx ExprContext, self *term, firstChildValue any) (ds map
missingFields = append(missingFields, field) missingFields = append(missingFields, field)
} }
} }
err = fmt.Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", ")) err = iteratorTerm.children[0].Errorf("the data-source must provide a non-nil %q operator(s)", strings.Join(missingFields, ", "))
} }
} }
return return
} }
func evalIterator(ctx ExprContext, self *term) (v any, err error) { func evalIterator(ctx ExprContext, opTerm *term) (v any, err error) {
var firstChildValue any var firstChildValue any
var ds map[string]Functor var ds map[string]Functor
if firstChildValue, err = evalFirstChild(ctx, self); err != nil { if firstChildValue, err = evalFirstChild(ctx, opTerm); err != nil {
return return
} }
if ds, err = getDataSourceDict(ctx, self, firstChildValue); err != nil { if ds, err = getDataSourceDict(opTerm, firstChildValue); err != nil {
return return
} }
if ds != nil { if ds != nil {
dc := newDataCursor(ctx, ds) var dc *dataCursor
if initFunc, exists := ds[initName]; exists && initFunc != nil { if initFunc, exists := ds[InitName]; exists && initFunc != nil {
var args []any var args []any
if len(self.children) > 1 { var resource any
if args, err = evalTermArray(ctx, self.children[1:]); err != nil { if len(opTerm.children) > 1 {
if args, err = evalTermArray(ctx, opTerm.children[1:]); err != nil {
return return
} }
} else { } else {
args = []any{} args = []any{}
} }
initCtx := dc.ctx.Clone() initCtx := ctx.Clone()
if dc.resource, err = initFunc.Invoke(initCtx, initName, args); err != nil { if resource, err = initFunc.Invoke(initCtx, InitName, args); err != nil {
return return
} }
exportObjects(dc.ctx, initCtx) dcCtx := ctx.Clone()
exportObjects(dcCtx, initCtx)
dc = NewDataCursor(dcCtx, ds, resource)
} }
dc.nextFunc, _ = ds[nextName]
dc.currentFunc, _ = ds[currentName]
dc.cleanFunc, _ = ds[cleanName]
dc.resetFunc, _ = ds[resetName]
v = dc v = dc
} else if list, ok := firstChildValue.(*ListType); ok { } else if list, ok := firstChildValue.(*ListType); ok {
var args []any var args []any
if args, err = evalSibling(ctx, self.children, nil); err == nil { if args, err = evalSibling(ctx, opTerm.children, nil); err == nil {
v = NewListIterator(list, args) v = NewListIterator(list, args)
} }
} else { } else {
var list []any var list []any
if list, err = evalSibling(ctx, self.children, firstChildValue); err == nil { if list, err = evalSibling(ctx, opTerm.children, firstChildValue); err == nil {
v = NewArrayIterator(list) v = NewArrayIterator(list)
} }
} }
+2 -2
View File
@@ -21,8 +21,8 @@ func newListTerm(row, col int, args []*term) *term {
} }
// -------- list func // -------- list func
func evalList(ctx ExprContext, self *term) (v any, err error) { func evalList(ctx ExprContext, opTerm *term) (v any, err error) {
list, _ := self.value().([]*term) list, _ := opTerm.value().([]*term)
items := make(ListType, len(list)) items := make(ListType, len(list))
for i, tree := range list { for i, tree := range list {
var param any var param any
+2 -2
View File
@@ -17,8 +17,8 @@ func newLiteralTerm(tk *Token) *term {
} }
// -------- eval func // -------- eval func
func evalLiteral(ctx ExprContext, self *term) (v any, err error) { func evalLiteral(ctx ExprContext, opTerm *term) (v any, err error) {
v = self.tk.Value v = opTerm.tk.Value
return return
} }
+3 -3
View File
@@ -41,10 +41,10 @@ func newSelectorCaseTerm(row, col int, filterList *term, caseExpr Expr) *term {
} }
// -------- eval selector case // -------- eval selector case
func evalSelectorCase(ctx ExprContext, self *term) (v any, err error) { func evalSelectorCase(ctx ExprContext, opTerm *term) (v any, err error) {
var ok bool var ok bool
if v, ok = self.value().(*selectorCase); !ok { if v, ok = opTerm.value().(*selectorCase); !ok {
err = fmt.Errorf("selector-case expected, got %T", self.value()) err = fmt.Errorf("selector-case expected, got %T", opTerm.value())
} }
return return
} }
+2 -2
View File
@@ -21,9 +21,9 @@ func newVarTerm(tk *Token) *term {
} }
// -------- eval func // -------- eval func
func evalVar(ctx ExprContext, self *term) (v any, err error) { func evalVar(ctx ExprContext, opTerm *term) (v any, err error) {
var exists bool var exists bool
name := self.source() name := opTerm.source()
if v, exists = GetVar(ctx, name); !exists { if v, exists = GetVar(ctx, name); !exists {
if info, exists, _ := GetFuncInfo(ctx, name); exists { if info, exists, _ := GetFuncInfo(ctx, name); exists {
v = info.Functor() v = info.Functor()
+17 -13
View File
@@ -28,7 +28,7 @@ func assignCollectionItem(ctx ExprContext, collectionTerm, keyListTerm *term, va
if keyListValue, err = keyListTerm.compute(ctx); err != nil { if keyListValue, err = keyListTerm.compute(ctx); err != nil {
return return
} else if keyList, ok = keyListValue.(*ListType); !ok || len(*keyList) != 1 { } else if keyList, ok = keyListValue.(*ListType); !ok || len(*keyList) != 1 {
err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, typeName(keyListValue)) err = keyListTerm.Errorf("index/key specification expected, got %v [%s]", keyListValue, TypeName(keyListValue))
return return
} }
if keyValue = (*keyList)[0]; keyValue == nil { if keyValue = (*keyList)[0]; keyValue == nil {
@@ -41,7 +41,7 @@ func assignCollectionItem(ctx ExprContext, collectionTerm, keyListTerm *term, va
if index, ok := keyValue.(int64); ok { if index, ok := keyValue.(int64); ok {
err = collection.setItem(index, value) err = collection.setItem(index, value)
} else { } else {
err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, typeName(keyValue)) err = keyListTerm.Errorf("integer expected, got %v [%s]", keyValue, TypeName(keyValue))
} }
case *DictType: case *DictType:
err = collection.setItem(keyValue, value) err = collection.setItem(keyValue, value)
@@ -60,30 +60,34 @@ func assignValue(ctx ExprContext, leftTerm *term, v any) (err error) {
return return
} }
func evalAssign(ctx ExprContext, self *term) (v any, err error) { func evalAssign(ctx ExprContext, opTerm *term) (v any, err error) {
if err = self.checkOperands(); err != nil { if err = opTerm.checkOperands(); err != nil {
return return
} }
leftTerm := self.children[0] leftTerm := opTerm.children[0]
leftSym := leftTerm.symbol() leftSym := leftTerm.symbol()
if leftSym != SymVariable && leftSym != SymIndex { 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 return
} }
rightChild := self.children[1] rightChild := opTerm.children[1]
if v, err = rightChild.compute(ctx); err == nil { if v, err = rightChild.compute(ctx); err == nil {
if functor, ok := v.(Functor); ok { if functor, ok := v.(Functor); ok {
if info := functor.GetFunc(); info != nil { if leftSym == SymVariable {
ctx.RegisterFunc(leftTerm.source(), info.Functor(), info.ReturnType(), info.Params()) if info := functor.GetFunc(); info != nil {
} else if funcDef, ok := functor.(*exprFunctor); ok { ctx.RegisterFunc(leftTerm.source(), info.Functor(), info.ReturnType(), info.Params())
paramSpecs := ForAll(funcDef.params, func(p ExprFuncParam) ExprFuncParam { return p }) } else if funcDef, ok := functor.(*exprFunctor); ok {
paramSpecs := ForAll(funcDef.params, func(p ExprFuncParam) ExprFuncParam { return p })
ctx.RegisterFunc(leftTerm.source(), functor, typeAny, paramSpecs) ctx.RegisterFunc(leftTerm.source(), functor, TypeAny, paramSpecs)
} else {
err = opTerm.Errorf("unknown function %s()", rightChild.source())
}
} else { } else {
err = self.Errorf("unknown function %s()", rightChild.source()) err = assignValue(ctx, leftTerm, v)
} }
} else { } else {
err = assignValue(ctx, leftTerm, v) err = assignValue(ctx, leftTerm, v)
+18 -22
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 var rightValue any
if rightValue, err = self.evalPrefix(ctx); err != nil { if rightValue, err = opTerm.evalPrefix(ctx); err != nil {
return return
} }
if b, ok := toBool(rightValue); ok { if b, ok := ToBool(rightValue); ok {
v = !b v = !b
} else { } else {
err = self.errIncompatibleType(rightValue) err = opTerm.errIncompatibleType(rightValue)
} }
return return
} }
@@ -37,9 +37,7 @@ func evalNot(ctx ExprContext, self *term) (v any, err error) {
func newAndTerm(tk *Token) (inst *term) { func newAndTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
// class: classOperator,
// kind: kindBool,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priAnd, priority: priAnd,
@@ -48,7 +46,7 @@ func newAndTerm(tk *Token) (inst *term) {
} }
func evalAnd(ctx ExprContext, self *term) (v any, err error) { func evalAnd(ctx ExprContext, self *term) (v any, err error) {
if isEnabled(ctx, ControlBoolShortcut) { if CtrlIsEnabled(ctx, ControlBoolShortcut) {
v, err = evalAndWithShortcut(ctx, self) v, err = evalAndWithShortcut(ctx, self)
} else { } else {
v, err = evalAndWithoutShortcut(ctx, self) v, err = evalAndWithoutShortcut(ctx, self)
@@ -65,8 +63,8 @@ func evalAndWithoutShortcut(ctx ExprContext, self *term) (v any, err error) {
return return
} }
leftBool, lok = toBool(leftValue) leftBool, lok = ToBool(leftValue)
rightBool, rok = toBool(rightValue) rightBool, rok = ToBool(rightValue)
if lok && rok { if lok && rok {
v = leftBool && rightBool v = leftBool && rightBool
@@ -87,13 +85,13 @@ func evalAndWithShortcut(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if leftBool, lok := toBool(leftValue); !lok { if leftBool, lok := ToBool(leftValue); !lok {
err = fmt.Errorf("got %T as left operand type of 'and' operator, it must be bool", leftBool) err = fmt.Errorf("got %s as left operand type of 'AND' operator, it must be bool", TypeName(leftValue))
return return
} else if !leftBool { } else if !leftBool {
v = false v = false
} else if rightValue, err = self.children[1].compute(ctx); err == nil { } else if rightValue, err = self.children[1].compute(ctx); err == nil {
if rightBool, rok := toBool(rightValue); rok { if rightBool, rok := ToBool(rightValue); rok {
v = rightBool v = rightBool
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
@@ -106,9 +104,7 @@ func evalAndWithShortcut(ctx ExprContext, self *term) (v any, err error) {
func newOrTerm(tk *Token) (inst *term) { func newOrTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
// class: classOperator,
// kind: kindBool,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priOr, priority: priOr,
@@ -117,7 +113,7 @@ func newOrTerm(tk *Token) (inst *term) {
} }
func evalOr(ctx ExprContext, self *term) (v any, err error) { func evalOr(ctx ExprContext, self *term) (v any, err error) {
if isEnabled(ctx, ControlBoolShortcut) { if CtrlIsEnabled(ctx, ControlBoolShortcut) {
v, err = evalOrWithShortcut(ctx, self) v, err = evalOrWithShortcut(ctx, self)
} else { } else {
v, err = evalOrWithoutShortcut(ctx, self) v, err = evalOrWithoutShortcut(ctx, self)
@@ -134,8 +130,8 @@ func evalOrWithoutShortcut(ctx ExprContext, self *term) (v any, err error) {
return return
} }
leftBool, lok = toBool(leftValue) leftBool, lok = ToBool(leftValue)
rightBool, rok = toBool(rightValue) rightBool, rok = ToBool(rightValue)
if lok && rok { if lok && rok {
v = leftBool || rightBool v = leftBool || rightBool
@@ -156,13 +152,13 @@ func evalOrWithShortcut(ctx ExprContext, self *term) (v any, err error) {
return return
} }
if leftBool, lok := toBool(leftValue); !lok { if leftBool, lok := ToBool(leftValue); !lok {
err = fmt.Errorf("got %T as left operand type of 'or' operator, it must be bool", leftBool) err = fmt.Errorf("got %s as left operand type of 'OR' operator, it must be bool", TypeName(leftValue))
return return
} else if leftBool { } else if leftBool {
v = true v = true
} else if rightValue, err = self.children[1].compute(ctx); err == nil { } else if rightValue, err = self.children[1].compute(ctx); err == nil {
if rightBool, rok := toBool(rightValue); rok { if rightBool, rok := ToBool(rightValue); rok {
v = rightBool v = rightBool
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
+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 var childValue any
if childValue, err = self.evalPrefix(ctx); err != nil { if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return return
} }
@@ -37,11 +37,11 @@ func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
if ImportInContext(module) { if ImportInContext(module) {
count++ count++
} else { } else {
err = self.Errorf("unknown module %q", module) err = opTerm.Errorf("unknown builtin module %q", module)
break break
} }
} else { } else {
err = self.Errorf("expected string at item nr %d, got %T", it.Index()+1, moduleSpec) err = opTerm.Errorf("expected string at item nr %d, got %s", it.Index()+1, TypeName(moduleSpec))
break break
} }
} }
+2 -2
View File
@@ -16,8 +16,8 @@ func newButTerm(tk *Token) (inst *term) {
} }
} }
func evalBut(ctx ExprContext, self *term) (v any, err error) { func evalBut(ctx ExprContext, opTerm *term) (v any, err error) {
_, v, err = self.evalInfix(ctx) _, v, err = opTerm.evalInfix(ctx)
return return
} }
-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, pfOptional|pfRepeat),
})
} else {
v = rightValue
ctx.UnsafeSetVar(leftTerm.source(), rightValue)
}
}
return
}
// init
func init() {
registerTermConstructor(SymDoubleQuestion, newNullCoalesceTerm)
registerTermConstructor(SymQuestionEqual, newCoalesceAssignTerm)
}
+10 -7
View File
@@ -16,25 +16,28 @@ func newContextTerm(tk *Token) (inst *term) {
} }
} }
func evalContextValue(ctx ExprContext, self *term) (v any, err error) { func evalContextValue(ctx ExprContext, opTerm *term) (v any, err error) {
var childValue any var childValue any
var sourceCtx ExprContext var sourceCtx ExprContext
if self.children == nil || len(self.children) == 0 { if opTerm.children == nil || len(opTerm.children) == 0 {
sourceCtx = ctx 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 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 { if dc, ok := childValue.(*dataCursor); ok {
sourceCtx = dc.ctx sourceCtx = dc.ctx
} }
} }
if sourceCtx != nil { if sourceCtx != nil {
if formatter, ok := sourceCtx.(Formatter); ok { if formatter, ok := sourceCtx.(DictFormat); ok {
v = formatter.ToDict()
} else if formatter, ok := sourceCtx.(Formatter); ok {
v = formatter.ToString(0) v = formatter.ToString(0)
} else { } else {
keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' }) // keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
keys := sourceCtx.EnumVars(nil)
d := make(map[string]any) d := make(map[string]any)
for _, key := range keys { for _, key := range keys {
d[key], _ = sourceCtx.GetVar(key) d[key], _ = sourceCtx.GetVar(key)
@@ -46,7 +49,7 @@ func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
v = d v = d
} }
} else { } else {
err = self.errIncompatibleType(childValue) err = opTerm.errIncompatibleType(childValue)
} }
return return
} }
+2 -2
View File
@@ -16,8 +16,8 @@ func newExportAllTerm(tk *Token) (inst *term) {
} }
} }
func evalExportAll(ctx ExprContext, self *term) (v any, err error) { func evalExportAll(ctx ExprContext, opTerm *term) (v any, err error) {
enable(ctx, control_export_all) CtrlEnable(ctx, control_export_all)
return return
} }
+124
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)
}
+6 -6
View File
@@ -15,17 +15,17 @@ 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 var leftValue, rightValue any
if err = self.checkOperands(); err != nil { if err = opTerm.checkOperands(); err != nil {
return return
} }
if leftValue, err = self.children[0].compute(ctx); err != nil { if leftValue, err = opTerm.children[0].compute(ctx); err != nil {
return return
} }
indexTerm := self.children[1] indexTerm := opTerm.children[1]
switch unboxedValue := leftValue.(type) { switch unboxedValue := leftValue.(type) {
case ExtIterator: case ExtIterator:
@@ -41,8 +41,8 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
err = indexTerm.tk.ErrorExpectedGot("identifier") err = indexTerm.tk.ErrorExpectedGot("identifier")
} }
default: default:
if rightValue, err = self.children[1].compute(ctx); err == nil { if rightValue, err = opTerm.children[1].compute(ctx); err == nil {
err = self.errIncompatibleTypes(leftValue, rightValue) err = opTerm.errIncompatibleTypes(leftValue, rightValue)
} }
} }
return 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 var leftValue any
if leftValue, err = self.evalPrefix(ctx); err != nil { if leftValue, err = opTerm.evalPrefix(ctx); err != nil {
return return
} }
@@ -36,7 +36,7 @@ func evalFact(ctx ExprContext, self *term) (v any, err error) {
err = fmt.Errorf("factorial of a negative integer (%d) is not allowed", i) err = fmt.Errorf("factorial of a negative integer (%d) is not allowed", i)
} }
} else { } else {
err = self.errIncompatibleType(leftValue) err = opTerm.errIncompatibleType(leftValue)
} }
return return
} }
+2 -2
View File
@@ -24,12 +24,12 @@ func newFractionTerm(tk *Token) *term {
} }
// -------- eval func // -------- eval func
func evalFraction(ctx ExprContext, self *term) (v any, err error) { func evalFraction(ctx ExprContext, opTerm *term) (v any, err error) {
var numValue, denValue any var numValue, denValue any
var num, den int64 var num, den int64
var ok bool var ok bool
if numValue, denValue, err = self.evalInfix(ctx); err != nil { if numValue, denValue, err = opTerm.evalInfix(ctx); err != nil {
return return
} }
if num, ok = numValue.(int64); !ok { if num, ok = numValue.(int64); !ok {
+3 -3
View File
@@ -21,10 +21,10 @@ func newInTerm(tk *Token) (inst *term) {
// return // return
// } // }
func evalIn(ctx ExprContext, self *term) (v any, err error) { func evalIn(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return return
} }
@@ -35,7 +35,7 @@ func evalIn(ctx ExprContext, self *term) (v any, err error) {
dict, _ := rightValue.(*DictType) dict, _ := rightValue.(*DictType)
v = dict.hasKey(leftValue) v = dict.hasKey(leftValue)
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = opTerm.errIncompatibleTypes(leftValue, rightValue)
} }
return return
} }
+13 -10
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 var childValue any
if childValue, err = self.evalPrefix(ctx); err != nil { if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return return
} }
count := 0 count := 0
if IsList(childValue) { if IsList(childValue) {
list, _ := childValue.([]any) list, _ := childValue.(*ListType)
for i, filePathSpec := range list { for i, filePathSpec := range *list {
if filePath, ok := filePathSpec.(string); ok { if filePath, ok := filePathSpec.(string); ok {
if v, err = EvalFile(ctx, filePath); err == nil { if v, err = EvalFile(ctx, filePath); err == nil {
count++ count++
} else { } else {
err = self.Errorf("can't load file %q", filePath) err = opTerm.Errorf("can't load file %q", filePath)
break break
} }
} else { } else {
err = self.Errorf("expected string at item nr %d, got %T", i+1, filePathSpec) err = opTerm.Errorf("expected string at item nr %d, got %T", i+1, filePathSpec)
break break
} }
} }
} else if IsString(childValue) { } else if IsString(childValue) {
filePath, _ := childValue.(string) filePath, _ := childValue.(string)
v, err = EvalFile(ctx, filePath) if v, err = EvalFile(ctx, filePath); err == nil {
count++
}
} else { } else {
err = self.errIncompatibleType(childValue) err = opTerm.errIncompatibleType(childValue)
} }
if err == nil { if err != nil {
v = count //v = count
v = nil
} }
return return
} }
+28 -16
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] index = (*indexList)[0]
return 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) { func verifyIndex(indexTerm *term, indexList *ListType, maxValue int) (index int, err error) {
var v int 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 { if v < 0 && v >= -maxValue {
v = maxValue + v v = maxValue + v
} }
@@ -40,11 +40,14 @@ func verifyRange(indexTerm *term, indexList *ListType, maxValue int) (startIndex
v, _ := ((*indexList)[0]).(*intPair) v, _ := ((*indexList)[0]).(*intPair)
startIndex = v.a startIndex = v.a
endIndex = v.b endIndex = v.b
if endIndex == ConstLastIndex {
endIndex = maxValue
}
if startIndex < 0 && startIndex >= -maxValue { if startIndex < 0 && startIndex >= -maxValue {
startIndex = maxValue + startIndex startIndex = maxValue + startIndex
} }
if endIndex < 0 && endIndex >= -maxValue { if endIndex < 0 && endIndex >= -maxValue {
endIndex = maxValue + endIndex + 1 endIndex = maxValue + endIndex
} }
if startIndex < 0 || startIndex > maxValue { if startIndex < 0 || startIndex > maxValue {
err = indexTerm.Errorf("range start-index %d is out of bounds", startIndex) err = indexTerm.Errorf("range start-index %d is out of bounds", startIndex)
@@ -56,18 +59,18 @@ func verifyRange(indexTerm *term, indexList *ListType, maxValue int) (startIndex
return 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 leftValue, rightValue any
var indexList *ListType var indexList *ListType
var ok bool var ok bool
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return return
} }
indexTerm := self.children[1] indexTerm := opTerm.children[1]
if indexList, ok = rightValue.(*ListType); !ok { if indexList, ok = rightValue.(*ListType); !ok {
err = self.Errorf("invalid index expression") err = opTerm.Errorf("invalid index expression")
return return
} else if len(*indexList) != 1 { } else if len(*indexList) != 1 {
err = indexTerm.Errorf("one index only is allowed") err = indexTerm.Errorf("one index only is allowed")
@@ -87,15 +90,9 @@ func evalIndex(ctx ExprContext, self *term) (v any, err error) {
v = string(unboxedValue[index]) v = string(unboxedValue[index])
} }
case *DictType: case *DictType:
var ok bool v, err = getDictItem(unboxedValue, indexTerm, indexList, rightValue)
var indexValue any
if indexValue, err = verifyKey(indexTerm, indexList); err == nil {
if v, ok = (*unboxedValue)[indexValue]; !ok {
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
}
}
default: default:
err = self.errIncompatibleTypes(leftValue, rightValue) err = opTerm.errIncompatibleTypes(leftValue, rightValue)
} }
} else if isIntPair((*indexList)[0]) { } else if isIntPair((*indexList)[0]) {
switch unboxedValue := leftValue.(type) { switch unboxedValue := leftValue.(type) {
@@ -111,7 +108,22 @@ func evalIndex(ctx ExprContext, self *term) (v any, err error) {
v = unboxedValue[start:end] v = unboxedValue[start:end]
} }
default: default:
err = self.errIncompatibleTypes(leftValue, rightValue) err = opTerm.errIncompatibleTypes(leftValue, rightValue)
}
} else if IsDict(leftValue) {
d := leftValue.(*DictType)
v, err = getDictItem(d, indexTerm, indexList, rightValue)
}
return
}
func getDictItem(d *DictType, indexTerm *term, indexList *ListType, rightValue any) (v any, err error) {
var ok bool
var indexValue any
if indexValue, err = verifyKey(indexList); err == nil {
if v, ok = (*d)[indexValue]; !ok {
err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
} }
} }
return return
+10 -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 var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return return
} }
@@ -37,19 +37,19 @@ func evalInsert(ctx ExprContext, self *term) (v any, err error) {
list, _ := rightValue.(*ListType) list, _ := rightValue.(*ListType)
newList := append(ListType{leftValue}, *list...) newList := append(ListType{leftValue}, *list...)
v = &newList v = &newList
if self.children[1].symbol() == SymVariable { if opTerm.children[1].symbol() == SymVariable {
ctx.UnsafeSetVar(self.children[1].source(), v) ctx.UnsafeSetVar(opTerm.children[1].source(), v)
} }
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = opTerm.errIncompatibleTypes(leftValue, rightValue)
} }
return return
} }
func evalAppend(ctx ExprContext, self *term) (v any, err error) { func evalAppend(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return return
} }
@@ -57,11 +57,11 @@ func evalAppend(ctx ExprContext, self *term) (v any, err error) {
list, _ := leftValue.(*ListType) list, _ := leftValue.(*ListType)
newList := append(*list, rightValue) newList := append(*list, rightValue)
v = &newList v = &newList
if self.children[0].symbol() == SymVariable { if opTerm.children[0].symbol() == SymVariable {
ctx.UnsafeSetVar(self.children[0].source(), v) ctx.UnsafeSetVar(opTerm.children[0].source(), v)
} }
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = opTerm.errIncompatibleTypes(leftValue, rightValue)
} }
return 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 var childValue any
if childValue, err = self.evalPrefix(ctx); err != nil { if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return return
} }
if it, ok := childValue.(Iterator); ok { if it, ok := childValue.(Iterator); ok {
v, err = it.Current() v, err = it.Current()
} else { } else {
err = self.errIncompatibleType(childValue) err = opTerm.errIncompatibleType(childValue)
} }
return return
} }
// init // init
func init() { func init() {
registerTermConstructor(SymOpenClosedRound, newIterValueTerm) // registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
registerTermConstructor(SymCaret, newIterValueTerm)
} }
+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 var childValue any
if childValue, err = self.evalPrefix(ctx); err != nil { if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return return
} }
@@ -30,18 +30,18 @@ func evalLength(ctx ExprContext, self *term) (v any, err error) {
s, _ := childValue.(string) s, _ := childValue.(string)
v = int64(len(s)) v = int64(len(s))
} else if IsDict(childValue) { } else if IsDict(childValue) {
// m, _ := childValue.(map[any]any)
m, _ := childValue.(*DictType) m, _ := childValue.(*DictType)
v = int64(len(*m)) v = int64(len(*m))
} else if it, ok := childValue.(Iterator); ok { } else if it, ok := childValue.(Iterator); ok {
if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(countName) { v = int64(it.Count())
count, _ := extIt.CallOperation(countName, nil) // if extIt, ok := childValue.(ExtIterator); ok && extIt.HasOperation(CountName) {
v, _ = toInt(count, "") // count, _ := extIt.CallOperation(CountName, nil)
} else { // v, _ = ToGoInt(count, "")
v = int64(it.Index() + 1) // } else {
} // v = int64(it.Index() + 1)
// }
} else { } else {
err = self.errIncompatibleType(childValue) err = opTerm.errIncompatibleType(childValue)
} }
return return
} }
+36
View File
@@ -0,0 +1,36 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-plugin.go
package expr
//-------- plugin term
func newPluginTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priSign,
evalFunc: evalPlugin,
}
}
func evalPlugin(ctx ExprContext, opTerm *term) (v any, err error) {
var childValue any
var count int
if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return
}
if count, err = importPluginFromSearchPath(childValue); err == nil {
v = int64(count)
}
return
}
// init
func init() {
registerTermConstructor(SymKwPlugin, newPluginTerm)
}
+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 var childValue any
if childValue, err = self.evalPrefix(ctx); err != nil { if childValue, err = opTerm.evalPrefix(ctx); err != nil {
return return
} }
if it, ok := childValue.(Iterator); ok { if it, ok := childValue.(Iterator); ok {
v, err = it.Next() v, err = it.Next()
} else if IsInteger(childValue) && self.children[0].symbol() == SymVariable { } else if IsInteger(childValue) && opTerm.children[0].symbol() == SymVariable {
v = childValue v = childValue
i, _ := childValue.(int64) i, _ := childValue.(int64)
ctx.SetVar(self.children[0].source(), i+1) ctx.SetVar(opTerm.children[0].source(), i+1)
} else { } else {
err = self.errIncompatibleType(childValue) err = opTerm.errIncompatibleType(childValue)
}
return
}
// -------- 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 return
} }
@@ -38,4 +69,5 @@ func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
// init // init
func init() { func init() {
registerTermConstructor(SymDoublePlus, newPostIncTerm) registerTermConstructor(SymDoublePlus, newPostIncTerm)
registerTermConstructor(SymDoubleMinus, newPostDecTerm)
} }
+14 -20
View File
@@ -14,8 +14,6 @@ import (
func newMultiplyTerm(tk *Token) (inst *term) { func newMultiplyTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
// class: classOperator,
// kind: kindUnknown,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priProduct, priority: priProduct,
@@ -23,10 +21,10 @@ func newMultiplyTerm(tk *Token) (inst *term) {
} }
} }
func evalMultiply(ctx ExprContext, self *term) (v any, err error) { func evalMultiply(ctx ExprContext, prodTerm *term) (v any, err error) {
var leftValue, rightValue any var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { if leftValue, rightValue, err = prodTerm.evalInfix(ctx); err != nil {
return return
} }
@@ -45,7 +43,7 @@ func evalMultiply(ctx ExprContext, self *term) (v any, err error) {
v = leftInt * rightInt v = leftInt * rightInt
} }
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = prodTerm.errIncompatibleTypes(leftValue, rightValue)
} }
return return
} }
@@ -55,8 +53,6 @@ func evalMultiply(ctx ExprContext, self *term) (v any, err error) {
func newDivideTerm(tk *Token) (inst *term) { func newDivideTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
// class: classOperator,
// kind: kindUnknown,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priProduct, priority: priProduct,
@@ -64,10 +60,10 @@ func newDivideTerm(tk *Token) (inst *term) {
} }
} }
func evalDivide(ctx ExprContext, self *term) (v any, err error) { func evalDivide(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return return
} }
@@ -90,7 +86,7 @@ func evalDivide(ctx ExprContext, self *term) (v any, err error) {
} }
} }
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = opTerm.errIncompatibleTypes(leftValue, rightValue)
} }
return return
} }
@@ -107,10 +103,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 var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { if leftValue, rightValue, err = floatDivTerm.evalInfix(ctx); err != nil {
return return
} }
@@ -122,18 +118,16 @@ func evalDivideAsFloat(ctx ExprContext, self *term) (v any, err error) {
v = numAsFloat(leftValue) / d v = numAsFloat(leftValue) / d
} }
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = floatDivTerm.errIncompatibleTypes(leftValue, rightValue)
} }
return return
} }
//-------- reminder term //-------- reminder term
func newReminderTerm(tk *Token) (inst *term) { func newRemainderTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
// class: classOperator,
// kind: kindUnknown,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priProduct, priority: priProduct,
@@ -141,10 +135,10 @@ func newReminderTerm(tk *Token) (inst *term) {
} }
} }
func evalReminder(ctx ExprContext, self *term) (v any, err error) { func evalReminder(ctx ExprContext, ramainderTerm *term) (v any, err error) {
var leftValue, rightValue any var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { if leftValue, rightValue, err = ramainderTerm.evalInfix(ctx); err != nil {
return return
} }
@@ -157,7 +151,7 @@ func evalReminder(ctx ExprContext, self *term) (v any, err error) {
v = leftInt % rightInt v = leftInt % rightInt
} }
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = ramainderTerm.errIncompatibleTypes(leftValue, rightValue)
} }
return return
} }
@@ -167,5 +161,5 @@ func init() {
registerTermConstructor(SymStar, newMultiplyTerm) registerTermConstructor(SymStar, newMultiplyTerm)
registerTermConstructor(SymSlash, newDivideTerm) registerTermConstructor(SymSlash, newDivideTerm)
registerTermConstructor(SymDotSlash, newDivideAsFloatTerm) registerTermConstructor(SymDotSlash, newDivideAsFloatTerm)
registerTermConstructor(SymPercent, newReminderTerm) registerTermConstructor(SymPercent, newRemainderTerm)
} }
+9 -9
View File
@@ -12,7 +12,7 @@ type intPair struct {
} }
func (p *intPair) TypeName() string { func (p *intPair) TypeName() string {
return typePair return TypePair
} }
func (p *intPair) ToString(opt FmtOpt) string { func (p *intPair) ToString(opt FmtOpt) string {
@@ -29,30 +29,30 @@ func newRangeTerm(tk *Token) (inst *term) {
tk: *tk, tk: *tk,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priDot, priority: priRange,
evalFunc: evalRange, evalFunc: evalRange,
} }
} }
func evalRange(ctx ExprContext, self *term) (v any, err error) { func evalRange(ctx ExprContext, opTerm *term) (v any, err error) {
var leftValue, rightValue any var leftValue, rightValue any
// if err = self.checkOperands(); err != nil { // if err = self.checkOperands(); err != nil {
// return // return
// } // }
if len(self.children) == 0 { if len(opTerm.children) == 0 {
leftValue = int64(0) leftValue = int64(0)
rightValue = int64(-1) rightValue = int64(-1)
} else if len(self.children) == 1 { } else if len(opTerm.children) == 1 {
if leftValue, err = self.children[0].compute(ctx); err != nil { if leftValue, err = opTerm.children[0].compute(ctx); err != nil {
return return
} }
rightValue = int64(-1) rightValue = int64(ConstLastIndex)
} else if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { } else if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return return
} }
if !(IsInteger(leftValue) && IsInteger(rightValue)) { if !(IsInteger(leftValue) && IsInteger(rightValue)) {
err = self.errIncompatibleTypes(leftValue, rightValue) err = opTerm.errIncompatibleTypes(leftValue, rightValue)
return return
} }
+17 -17
View File
@@ -45,10 +45,10 @@ func equals(a, b any, deepCmp deepFuncTemplate) (eq bool, err error) {
return 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 var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return return
} }
@@ -68,9 +68,9 @@ func newNotEqualTerm(tk *Token) (inst *term) {
} }
} }
func evalNotEqual(ctx ExprContext, self *term) (v any, err error) { func evalNotEqual(ctx ExprContext, opTerm *term) (v any, err error) {
if v, err = evalEqual(ctx, self); err == nil { if v, err = evalEqual(ctx, opTerm); err == nil {
b, _ := toBool(v) b, _ := ToBool(v)
v = !b v = !b
} }
return return
@@ -119,13 +119,13 @@ func lessThan(self *term, a, b any) (isLess bool, err error) {
return 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 var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return return
} }
v, err = lessThan(self, leftValue, rightValue) v, err = lessThan(opTerm, leftValue, rightValue)
return return
} }
@@ -154,14 +154,14 @@ func lessThanOrEqual(self *term, a, b any) (isLessEq bool, err error) {
return 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 var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return return
} }
v, err = lessThanOrEqual(self, leftValue, rightValue) v, err = lessThanOrEqual(opTerm, leftValue, rightValue)
return 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 var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return return
} }
v, err = lessThan(self, rightValue, leftValue) v, err = lessThan(opTerm, rightValue, leftValue)
return 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 var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { if leftValue, rightValue, err = opTerm.evalInfix(ctx); err != nil {
return return
} }
v, err = lessThanOrEqual(self, rightValue, leftValue) v, err = lessThanOrEqual(opTerm, rightValue, leftValue)
return return
} }
+7 -7
View File
@@ -19,17 +19,17 @@ func newSelectorTerm(tk *Token) (inst *term) {
func trySelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (match bool, selectedValue any, err error) { func trySelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (match bool, selectedValue any, err error) {
caseData, _ := caseSel.(*selectorCase) caseData, _ := caseSel.(*selectorCase)
if caseData.filterList == nil { if caseData.filterList == nil {
selectedValue, err = caseData.caseExpr.eval(ctx, false) selectedValue, err = caseData.caseExpr.Eval(ctx)
match = true match = true
} else if filterList, ok := caseData.filterList.value().([]*term); ok { } else if filterList, ok := caseData.filterList.value().([]*term); ok {
if len(filterList) == 0 && exprValue == int64(caseIndex) { if len(filterList) == 0 && exprValue == int64(caseIndex) {
selectedValue, err = caseData.caseExpr.eval(ctx, false) selectedValue, err = caseData.caseExpr.Eval(ctx)
match = true match = true
} else { } else {
var caseValue any var caseValue any
for _, caseTerm := range filterList { for _, caseTerm := range filterList {
if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue { if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue {
selectedValue, err = caseData.caseExpr.eval(ctx, false) selectedValue, err = caseData.caseExpr.Eval(ctx)
match = true match = true
break break
} }
@@ -39,19 +39,19 @@ func trySelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (ma
return return
} }
func evalSelector(ctx ExprContext, self *term) (v any, err error) { func evalSelector(ctx ExprContext, opTerm *term) (v any, err error) {
var exprValue any var exprValue any
var match bool var match bool
if err = self.checkOperands(); err != nil { if err = opTerm.checkOperands(); err != nil {
return return
} }
exprTerm := self.children[0] exprTerm := opTerm.children[0]
if exprValue, err = exprTerm.compute(ctx); err != nil { if exprValue, err = exprTerm.compute(ctx); err != nil {
return return
} }
caseListTerm := self.children[1] caseListTerm := opTerm.children[1]
caseList, _ := caseListTerm.value().([]*term) caseList, _ := caseListTerm.value().([]*term)
for i, caseTerm := range caseList { for i, caseTerm := range caseList {
caseSel := caseTerm.value() caseSel := caseTerm.value()
+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 var rightValue any
if rightValue, err = self.evalPrefix(ctx); err != nil { if rightValue, err = opTerm.evalPrefix(ctx); err != nil {
return return
} }
if IsFloat(rightValue) { if IsFloat(rightValue) {
if self.tk.Sym == SymChangeSign { if opTerm.tk.Sym == SymChangeSign {
f, _ := rightValue.(float64) f, _ := rightValue.(float64)
v = -f v = -f
} else { } else {
v = rightValue v = rightValue
} }
} else if IsInteger(rightValue) { } else if IsInteger(rightValue) {
if self.tk.Sym == SymChangeSign { if opTerm.tk.Sym == SymChangeSign {
i, _ := rightValue.(int64) i, _ := rightValue.(int64)
v = -i v = -i
} else { } else {
v = rightValue v = rightValue
} }
} else { } else {
err = self.errIncompatibleType(rightValue) err = opTerm.errIncompatibleType(rightValue)
} }
return return
} }
+8 -12
View File
@@ -21,10 +21,10 @@ func newPlusTerm(tk *Token) (inst *term) {
} }
} }
func evalPlus(ctx ExprContext, self *term) (v any, err error) { func evalPlus(ctx ExprContext, plusTerm *term) (v any, err error) {
var leftValue, rightValue any var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { if leftValue, rightValue, err = plusTerm.evalInfix(ctx); err != nil {
return return
} }
@@ -44,12 +44,8 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
rightList, _ = rightValue.(*ListType) rightList, _ = rightValue.(*ListType)
sumList := make(ListType, 0, len(*leftList)+len(*rightList)) sumList := make(ListType, 0, len(*leftList)+len(*rightList))
for _, item := range *leftList { sumList = append(sumList, *leftList...)
sumList = append(sumList, item) sumList = append(sumList, *rightList...)
}
for _, item := range *rightList {
sumList = append(sumList, item)
}
v = &sumList v = &sumList
} else if (isFraction(leftValue) && IsNumber(rightValue)) || (isFraction(rightValue) && IsNumber(leftValue)) { } else if (isFraction(leftValue) && IsNumber(rightValue)) || (isFraction(rightValue) && IsNumber(leftValue)) {
if IsFloat(leftValue) || IsFloat(rightValue) { if IsFloat(leftValue) || IsFloat(rightValue) {
@@ -64,7 +60,7 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
c.merge(rightDict) c.merge(rightDict)
v = c v = c
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = plusTerm.errIncompatibleTypes(leftValue, rightValue)
} }
return return
} }
@@ -81,10 +77,10 @@ func newMinusTerm(tk *Token) (inst *term) {
} }
} }
func evalMinus(ctx ExprContext, self *term) (v any, err error) { func evalMinus(ctx ExprContext, minusTerm *term) (v any, err error) {
var leftValue, rightValue any var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { if leftValue, rightValue, err = minusTerm.evalInfix(ctx); err != nil {
return return
} }
@@ -109,7 +105,7 @@ func evalMinus(ctx ExprContext, self *term) (v any, err error) {
} }
v = &diffList v = &diffList
} else { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = minusTerm.errIncompatibleTypes(leftValue, rightValue)
} }
return return
} }
+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)
}
+118 -53
View File
@@ -11,23 +11,26 @@ import (
//-------- parser //-------- parser
type parser struct { type parser struct {
ctx ExprContext
} }
func NewParser(ctx ExprContext) (p *parser) { func NewParser() (p *parser) {
p = &parser{ p = &parser{}
ctx: ctx,
}
return p return p
} }
func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token) (tree *term, err error) { func (parser *parser) Next(scanner *scanner) (tk *Token) {
for tk = scanner.Next(); tk.IsSymbol(SymComment); tk = scanner.Next() {
}
return
}
func (parser *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token) (tree *term, err error) {
args := make([]*term, 0, 10) args := make([]*term, 0, 10)
itemExpected := false itemExpected := false
lastSym := SymUnknown lastSym := SymUnknown
for lastSym != SymClosedRound && lastSym != SymEos { for lastSym != SymClosedRound && lastSym != SymEos {
var subTree *ast var subTree *ast
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err != nil { if subTree, err = parser.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err != nil {
break break
} }
prev := scanner.Previous() prev := scanner.Previous()
@@ -51,7 +54,7 @@ func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token)
return return
} }
func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) { func (parser *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
// Example: "add = func(x,y) {x+y} // Example: "add = func(x,y) {x+y}
var body *ast var body *ast
args := make([]*term, 0) args := make([]*term, 0)
@@ -60,15 +63,15 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
itemExpected := false itemExpected := false
tk := scanner.Previous() tk := scanner.Previous()
for lastSym != SymClosedRound && lastSym != SymEos { for lastSym != SymClosedRound && lastSym != SymEos {
tk = scanner.Next() tk = parser.Next(scanner)
if tk.IsSymbol(SymIdentifier) { if tk.IsSymbol(SymIdentifier) {
param := newTerm(tk) param := newTerm(tk)
args = append(args, param) args = append(args, param)
tk = scanner.Next() tk = parser.Next(scanner)
if tk.Sym == SymEqual { if tk.Sym == SymEqual {
var paramExpr *ast var paramExpr *ast
defaultParamsStarted = true defaultParamsStarted = true
if paramExpr, err = self.parseItem(scanner, false, SymComma, SymClosedRound); err != nil { if paramExpr, err = parser.parseItem(scanner, false, SymComma, SymClosedRound); err != nil {
break break
} }
param.forceChild(paramExpr.root) param.forceChild(paramExpr.root)
@@ -89,9 +92,9 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
err = tk.ErrorExpectedGot(")") err = tk.ErrorExpectedGot(")")
} }
if err == nil { if err == nil {
tk = scanner.Next() tk = parser.Next(scanner)
if tk.IsSymbol(SymOpenBrace) { if tk.IsSymbol(SymOpenBrace) {
body, err = self.parseGeneral(scanner, true, true, SymClosedBrace) body, err = parser.parseGeneral(scanner, true, true, SymClosedBrace)
} else { } else {
err = tk.ErrorExpectedGot("{") err = tk.ErrorExpectedGot("{")
} }
@@ -107,7 +110,7 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
return return
} }
func (self *parser) parseList(scanner *scanner, parsingIndeces bool, allowVarRef bool) (subtree *term, err error) { func (parser *parser) parseList(scanner *scanner, parsingIndeces bool, allowVarRef bool) (subtree *term, err error) {
r, c := scanner.lastPos() r, c := scanner.lastPos()
args := make([]*term, 0) args := make([]*term, 0)
lastSym := SymUnknown lastSym := SymUnknown
@@ -115,7 +118,7 @@ func (self *parser) parseList(scanner *scanner, parsingIndeces bool, allowVarRef
for lastSym != SymClosedSquare && lastSym != SymEos { for lastSym != SymClosedSquare && lastSym != SymEos {
var subTree *ast var subTree *ast
zeroRequired := scanner.current.Sym == SymColon zeroRequired := scanner.current.Sym == SymColon
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil { if subTree, err = parser.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
root := subTree.root root := subTree.root
if root != nil { if root != nil {
if !parsingIndeces && root.symbol() == SymColon { if !parsingIndeces && root.symbol() == SymColon {
@@ -156,14 +159,14 @@ func (self *parser) parseList(scanner *scanner, parsingIndeces bool, allowVarRef
return return
} }
func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *term, err error) { func (parser *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
tk := scanner.Previous() tk := scanner.Previous()
args := make([]*term, 0) args := make([]*term, 0)
lastSym := SymUnknown lastSym := SymUnknown
itemExpected := false itemExpected := false
for lastSym != SymClosedRound && lastSym != SymEos { for lastSym != SymClosedRound && lastSym != SymEos {
var subTree *ast var subTree *ast
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil { if subTree, err = parser.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil {
if subTree.root != nil { if subTree.root != nil {
args = append(args, subTree.root) args = append(args, subTree.root)
} else if itemExpected { } else if itemExpected {
@@ -187,8 +190,8 @@ func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *t
return return
} }
func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, err error) { func (parser *parser) parseDictKey(scanner *scanner) (key any, err error) {
tk := scanner.Next() tk := parser.Next(scanner)
if tk.Sym == SymError { if tk.Sym == SymError {
err = tk.Error() err = tk.Error()
return return
@@ -197,7 +200,7 @@ func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, e
return return
} }
if tk.Sym == SymInteger || tk.Sym == SymString { if tk.Sym == SymInteger || tk.Sym == SymString {
tkSep := scanner.Next() tkSep := parser.Next(scanner)
if tkSep.Sym != SymColon { if tkSep.Sym != SymColon {
err = tkSep.ErrorExpectedGot(":") err = tkSep.ErrorExpectedGot(":")
} else { } else {
@@ -209,14 +212,14 @@ func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, e
return return
} }
func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree *term, err error) { func (parser *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
args := make(map[any]*term, 0) args := make(map[any]*term, 0)
lastSym := SymUnknown lastSym := SymUnknown
itemExpected := false itemExpected := false
for lastSym != SymClosedBrace && lastSym != SymEos { for lastSym != SymClosedBrace && lastSym != SymEos {
var subTree *ast var subTree *ast
var key any var key any
if key, err = self.parseDictKey(scanner, allowVarRef); err != nil { if key, err = parser.parseDictKey(scanner); err != nil {
break break
} else if key == nil { } else if key == nil {
tk := scanner.Previous() tk := scanner.Previous()
@@ -226,10 +229,10 @@ func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree
} }
break break
} }
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedBrace); err == nil { if subTree, err = parser.parseItem(scanner, allowVarRef, SymComma, SymClosedBrace); err == nil {
if subTree.root != nil { if subTree.root != nil {
args[key] = subTree.root args[key] = subTree.root
} else if key != nil { } else /*if key != nil*/ {
prev := scanner.Previous() prev := scanner.Previous()
err = prev.ErrorExpectedGot("dictionary-value") err = prev.ErrorExpectedGot("dictionary-value")
break break
@@ -251,10 +254,10 @@ func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree
return return
} }
func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaultCase bool) (caseTerm *term, err error) { func (parser *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaultCase bool) (caseTerm *term, err error) {
var filterList *term var filterList *term
var caseExpr *ast var caseExpr *ast
tk := scanner.Next() tk := parser.Next(scanner)
startRow := tk.row startRow := tk.row
startCol := tk.col startCol := tk.col
if tk.Sym == SymOpenSquare { if tk.Sym == SymOpenSquare {
@@ -262,10 +265,10 @@ func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaul
err = tk.Errorf("case list in default clause") err = tk.Errorf("case list in default clause")
return return
} }
if filterList, err = self.parseList(scanner, false, allowVarRef); err != nil { if filterList, err = parser.parseList(scanner, false, allowVarRef); err != nil {
return return
} }
tk = scanner.Next() tk = parser.Next(scanner)
startRow = tk.row startRow = tk.row
startCol = tk.col startCol = tk.col
} else if !defaultCase { } else if !defaultCase {
@@ -273,7 +276,7 @@ func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaul
} }
if tk.Sym == SymOpenBrace { if tk.Sym == SymOpenBrace {
if caseExpr, err = self.parseGeneral(scanner, true, allowVarRef, SymClosedBrace); err != nil { if caseExpr, err = parser.parseGeneral(scanner, true, allowVarRef, SymClosedBrace); err != nil {
return return
} }
} else { } else {
@@ -299,25 +302,25 @@ func addSelectorCase(selectorTerm, caseTerm *term) {
caseTerm.parent = selectorTerm caseTerm.parent = selectorTerm
} }
func (self *parser) parseSelector(scanner *scanner, tree *ast, allowVarRef bool) (selectorTerm *term, err error) { func (parser *parser) parseSelector(scanner *scanner, tree *ast, allowVarRef bool) (selectorTerm *term, err error) {
var caseTerm *term var caseTerm *term
tk := scanner.makeToken(SymSelector, '?') tk := scanner.makeToken(SymSelector, '?')
if selectorTerm, err = tree.addToken2(tk); err != nil { if selectorTerm, err = tree.addToken(tk); err != nil {
return return
} }
if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, false); err == nil { if caseTerm, err = parser.parseSelectorCase(scanner, allowVarRef, false); err == nil {
addSelectorCase(selectorTerm, caseTerm) addSelectorCase(selectorTerm, caseTerm)
} }
return return
} }
func (self *parser) parseItem(scanner *scanner, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) { func (parser *parser) parseItem(scanner *scanner, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
return self.parseGeneral(scanner, false, allowVarRef, termSymbols...) return parser.parseGeneral(scanner, false, allowVarRef, termSymbols...)
} }
func (self *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, err error) { func (parser *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, err error) {
return self.parseGeneral(scanner, true, false, termSymbols...) return parser.parseGeneral(scanner, true, false, termSymbols...)
} }
func couldBeACollection(t *term) bool { func couldBeACollection(t *term) bool {
@@ -328,17 +331,26 @@ func couldBeACollection(t *term) bool {
return sym == SymList || sym == SymString || sym == SymDict || sym == SymExpression || sym == SymVariable return sym == SymList || sym == SymString || sym == SymDict || sym == SymExpression || sym == SymVariable
} }
func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) { // func areSymbolsOutOfCtx(tk *Token, ctxTerm *term, syms ...Symbol) bool {
// var areOut = false
// if ctxTerm != nil {
// areOut = tk.IsOneOf(syms)
// }
// return areOut
// }
func (parser *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
var selectorTerm *term = nil var selectorTerm *term = nil
var currentTerm *term = nil var currentTerm *term = nil
var tk *Token var tk *Token
tree = NewAst() tree = NewAst()
firstToken := true firstToken := true
// lastSym := SymUnknown // lastSym := SymUnknown
for tk = scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); tk = scanner.Next() { for tk = parser.Next(scanner); err == nil && tk != nil && !tk.IsTerm(termSymbols); /*&& !areSymbolsOutOfCtx(tk, selectorTerm, SymColon, SymDoubleColon)*/ tk = parser.Next(scanner) {
if tk.Sym == SymComment { // if tk.Sym == SymComment {
continue // continue
} // }
if tk.Sym == SymSemiColon { if tk.Sym == SymSemiColon {
if allowForest { if allowForest {
@@ -366,21 +378,21 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
switch tk.Sym { switch tk.Sym {
case SymOpenRound: case SymOpenRound:
var subTree *ast var subTree *ast
if subTree, err = self.parseGeneral(scanner, false, allowVarRef, SymClosedRound); err == nil { if subTree, err = parser.parseGeneral(scanner, false, allowVarRef, SymClosedRound); err == nil {
subTree.root.priority = priValue subTree.root.priority = priValue
err = tree.addTerm(newExprTerm(subTree.root)) err = tree.addTerm(newExprTerm(subTree.root))
currentTerm = subTree.root currentTerm = subTree.root
} }
case SymFuncCall: case SymFuncCall:
var funcCallTerm *term var funcCallTerm *term
if funcCallTerm, err = self.parseFuncCall(scanner, allowVarRef, tk); err == nil { if funcCallTerm, err = parser.parseFuncCall(scanner, allowVarRef, tk); err == nil {
err = tree.addTerm(funcCallTerm) err = tree.addTerm(funcCallTerm)
currentTerm = funcCallTerm currentTerm = funcCallTerm
} }
case SymOpenSquare: case SymOpenSquare:
var listTerm *term var listTerm *term
parsingIndeces := couldBeACollection(currentTerm) parsingIndeces := couldBeACollection(currentTerm)
if listTerm, err = self.parseList(scanner, parsingIndeces, allowVarRef); err == nil { if listTerm, err = parser.parseList(scanner, parsingIndeces, allowVarRef); err == nil {
if parsingIndeces { if parsingIndeces {
indexTk := NewToken(listTerm.tk.row, listTerm.tk.col, SymIndex, listTerm.source()) indexTk := NewToken(listTerm.tk.row, listTerm.tk.col, SymIndex, listTerm.source())
indexTerm := newTerm(indexTk) indexTerm := newTerm(indexTk)
@@ -397,24 +409,24 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
err = currentTerm.Errorf(`selector-case outside of a selector context`) err = currentTerm.Errorf(`selector-case outside of a selector context`)
} else { } else {
var mapTerm *term var mapTerm *term
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil { if mapTerm, err = parser.parseDictionary(scanner, allowVarRef); err == nil {
err = tree.addTerm(mapTerm) err = tree.addTerm(mapTerm)
currentTerm = mapTerm currentTerm = mapTerm
} }
} }
case SymEqual: case SymEqual:
// if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil { // if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
currentTerm, err = tree.addToken2(tk) currentTerm, err = tree.addToken(tk)
// } // }
case SymFuncDef: case SymFuncDef:
var funcDefTerm *term var funcDefTerm *term
if funcDefTerm, err = self.parseFuncDef(scanner); err == nil { if funcDefTerm, err = parser.parseFuncDef(scanner); err == nil {
err = tree.addTerm(funcDefTerm) err = tree.addTerm(funcDefTerm)
currentTerm = funcDefTerm currentTerm = funcDefTerm
} }
case SymDollarRound: case SymDollarRound:
var iterDefTerm *term var iterDefTerm *term
if iterDefTerm, err = self.parseIterDef(scanner, allowVarRef); err == nil { if iterDefTerm, err = parser.parseIterDef(scanner, allowVarRef); err == nil {
err = tree.addTerm(iterDefTerm) err = tree.addTerm(iterDefTerm)
currentTerm = iterDefTerm currentTerm = iterDefTerm
} }
@@ -422,16 +434,16 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
if tk.source[0] == '@' && !allowVarRef { if tk.source[0] == '@' && !allowVarRef {
err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source) err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source)
} else { } else {
currentTerm, err = tree.addToken2(tk) currentTerm, err = tree.addToken(tk)
} }
case SymQuestion: case SymQuestion:
if selectorTerm, err = self.parseSelector(scanner, tree, allowVarRef); err == nil { if selectorTerm, err = parser.parseSelector(scanner, tree, allowVarRef); err == nil {
currentTerm = selectorTerm currentTerm = selectorTerm
} }
case SymColon, SymDoubleColon: case SymColon, SymDoubleColon:
var caseTerm *term var caseTerm *term
if selectorTerm != nil { if selectorTerm != nil {
if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil { if caseTerm, err = parser.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
addSelectorCase(selectorTerm, caseTerm) addSelectorCase(selectorTerm, caseTerm)
currentTerm = caseTerm currentTerm = caseTerm
if tk.Sym == SymDoubleColon { if tk.Sym == SymDoubleColon {
@@ -439,10 +451,16 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
} }
} }
} else { } else {
currentTerm, err = tree.addToken2(tk) currentTerm, err = tree.addToken(tk)
} }
if tk.IsSymbol(SymColon) {
// Colon outside a selector term acts like a separator
firstToken = true
}
case SymPlusEqual, SymMinusEqual, SymStarEqual:
currentTerm, err = parser.expandOpAssign(scanner, tree, tk, allowVarRef)
default: default:
currentTerm, err = tree.addToken2(tk) currentTerm, err = tree.addToken(tk)
} }
if currentTerm != nil && currentTerm.tk.Sym != SymSelector && currentTerm.parent != nil && currentTerm.parent.tk.Sym != SymSelector { if currentTerm != nil && currentTerm.tk.Sym != SymSelector && currentTerm.parent != nil && currentTerm.parent.tk.Sym != SymSelector {
@@ -451,6 +469,7 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
} }
// lastSym = tk.Sym // lastSym = tk.Sym
} }
if err == nil { if err == nil {
err = tk.Error() err = tk.Error()
} }
@@ -463,3 +482,49 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
// } // }
// return // return
// } // }
func (parser *parser) expandOpAssign(scanner * scanner, tree *ast, tk *Token, allowVarRef bool) (t *term, err error) {
var opSym Symbol
var opString string
if tree.root != nil {
switch tk.Sym {
case SymPlusEqual:
opString = "+"
opSym = SymPlus
case SymMinusEqual:
opString = "-"
opSym = SymMinus
case SymStarEqual:
opString = "*"
opSym = SymStar
default:
err = tk.Errorf("unsopported operator %q", tk.source)
return
}
leftExpr := tree.root.Clone()
leftExpr.setParent(nil)
if t, err = tree.addToken(NewToken(tk.row, tk.col, SymEqual, "=")); err == nil {
t = leftExpr
if err = tree.addTerm(leftExpr); err == nil {
if t, err = tree.addToken(NewToken(tk.row, tk.col, opSym, opString)); err == nil {
var subTree *ast
if subTree, err = parser.parseGeneral(scanner, false, allowVarRef, SymSemiColon, SymClosedRound, SymClosedBrace, SymClosedSquare); err == nil {
if scanner.Previous().IsOneOfA(SymSemiColon, SymClosedRound, SymClosedBrace, SymClosedSquare) {
if err = scanner.UnreadToken(); err != nil {
return
}
}
subTree.root.priority = priValue
err = tree.addTerm(newExprTerm(subTree.root))
t = subTree.root
}
}
}
}
} else {
err = tk.Errorf("left operand of %q must be a variable or a variable expression", tk)
}
return
}
+131
View File
@@ -0,0 +1,131 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// plugin.go
package expr
import (
"fmt"
"io"
"os"
"plugin"
"strings"
)
var pluginRegister map[string]*plugin.Plugin
func registerPlugin(name string, p *plugin.Plugin) (err error) {
if pluginExists(name) {
err = fmt.Errorf("plugin %q already loaded", name)
} else {
pluginRegister[name] = p
}
return
}
func pluginExists(name string) (exists bool) {
_, exists = pluginRegister[name]
return
}
func makePluginName(name string) (decorated string) {
var template string
if execName, err := os.Executable(); err != nil || !strings.Contains(execName, "debug") {
template = "expr-%s-plugin.so"
} else {
template = "expr-%s-plugin.so.debug"
}
decorated = fmt.Sprintf(template, name)
return
}
func importPlugin( /*ctx ExprContext,*/ dirList []string, name string) (err error) {
var filePath string
var p *plugin.Plugin
var sym plugin.Symbol
var moduleName string
var importFunc func(ExprContext)
var ok bool
decoratedName := makePluginName(name)
if filePath, err = makeFilepath(decoratedName, dirList); err != nil {
return
}
if p, err = plugin.Open(filePath); err != nil {
return
}
if sym, err = p.Lookup("MODULE_NAME"); err != nil {
return
}
if moduleName = *sym.(*string); moduleName == "" {
err = fmt.Errorf("plugin %q does not provide a valid module name", decoratedName)
return
}
if sym, err = p.Lookup("DEPENDENCIES"); err != nil {
return
}
if deps := *sym.(*[]string); len(deps) > 0 {
// var count int
if err = loadModules(dirList, deps); err != nil {
return
}
}
if sym, err = p.Lookup("ImportFuncs"); err != nil {
return
}
if importFunc, ok = sym.(func(ExprContext)); !ok {
err = fmt.Errorf("plugin %q does not provide a valid import function", decoratedName)
return
}
registerPlugin(moduleName, p)
importFunc(globalCtx)
return
}
func importPluginFromSearchPath(name any) (count int, err error) {
var moduleSpec any
dirList := buildSearchDirList("plugin", ENV_EXPR_PLUGIN_PATH)
count = 0
it := NewAnyIterator(name)
for moduleSpec, err = it.Next(); err == nil; moduleSpec, err = it.Next() {
if module, ok := moduleSpec.(string); ok {
if err = importPlugin(dirList, module); err != nil {
break
}
count++
} else {
err = fmt.Errorf("expected string as item nr %d, got %s", it.Index()+1, TypeName(moduleSpec))
break
}
}
if err == io.EOF {
err = nil
}
return
}
func loadModules(dirList []string, moduleNames []string) (err error) {
for _, name := range moduleNames {
if err1 := importPlugin(dirList, name); err1 != nil {
if !ImportInContext(name) {
err = err1
break
}
}
}
return
}
func init() {
pluginRegister = make(map[string]*plugin.Plugin)
}
+213 -188
View File
@@ -16,6 +16,7 @@ import (
type scanner struct { type scanner struct {
current *Token current *Token
prev *Token prev *Token
stage *Token
stream *bufio.Reader stream *bufio.Reader
row int row int
column int column int
@@ -49,250 +50,274 @@ func DefaultTranslations() map[Symbol]Symbol {
// return self.current // return self.current
// } // }
func (self *scanner) readChar() (ch byte, err error) { func (scanner *scanner) readChar() (ch byte, err error) {
if ch, err = self.stream.ReadByte(); err == nil { if ch, err = scanner.stream.ReadByte(); err == nil {
if ch == '\n' { if ch == '\n' {
self.row++ scanner.row++
self.column = 0 scanner.column = 0
} else { } else {
self.column++ scanner.column++
} }
} }
return return
} }
func (self *scanner) unreadChar() (err error) { func (scanner *scanner) unreadChar() (err error) {
if err = self.stream.UnreadByte(); err == nil { if err = scanner.stream.UnreadByte(); err == nil {
if self.column--; self.column == 0 { if scanner.column--; scanner.column == 0 {
if self.row--; self.row == 0 { if scanner.row--; scanner.row == 0 {
err = errors.New("unread beyond the stream boundary") err = errors.New("unread beyond the stream boundary")
} else { } else {
self.column = 1 scanner.column = 1
} }
} }
} }
return return
} }
func (self *scanner) lastPos() (r, c int) { func (scanner *scanner) UnreadToken() (err error) {
if self.prev != nil { if scanner.stage == nil {
r = self.prev.row scanner.stage = scanner.current
c = self.prev.col scanner.current = scanner.prev
} else {
err = fmt.Errorf("staging already present, currently one level only of staging is allowed")
} }
return return
} }
func (self *scanner) Previous() *Token { func (scanner *scanner) lastPos() (r, c int) {
return self.prev if scanner.prev != nil {
r = scanner.prev.row
c = scanner.prev.col
}
return
} }
func (self *scanner) Next() (tk *Token) { func (scanner *scanner) Previous() *Token {
self.prev = self.current return scanner.prev
tk = self.current }
self.current = self.fetchNextToken()
func (scanner *scanner) Next() (tk *Token) {
scanner.prev = scanner.current
tk = scanner.current
if scanner.stage != nil {
scanner.current = scanner.stage
scanner.stage = nil
} else {
scanner.current = scanner.fetchNextToken()
}
return tk return tk
} }
func (self *scanner) fetchNextToken() (tk *Token) { func (scanner *scanner) fetchNextToken() (tk *Token) {
var ch byte var ch byte
if err := self.skipBlanks(); err != nil { if err := scanner.skipBlanks(); err != nil {
return self.makeErrorToken(err) return scanner.makeErrorToken(err)
} }
escape := false escape := false
for { for {
ch, _ = self.readChar() ch, _ = scanner.readChar()
switch ch { switch ch {
case '+': case '+':
if next, _ := self.peek(); next == '+' { if next, _ := scanner.peek(); next == '+' {
tk = self.moveOn(SymDoublePlus, ch, next) tk = scanner.moveOn(SymDoublePlus, ch, next)
} else if next == '=' { } else if next == '=' {
tk = self.moveOn(SymPlusEqual, ch, next) tk = scanner.moveOn(SymPlusEqual, ch, next)
} else { } else {
tk = self.makeToken(SymPlus, ch) tk = scanner.makeToken(SymPlus, ch)
} }
case '-': case '-':
if next, _ := self.peek(); next == '-' { if next, _ := scanner.peek(); next == '-' {
tk = self.moveOn(SymDoubleMinus, ch, next) tk = scanner.moveOn(SymDoubleMinus, ch, next)
} else if next == '=' { } else if next == '=' {
tk = self.moveOn(SymMinusEqual, ch, next) tk = scanner.moveOn(SymMinusEqual, ch, next)
} else { } else {
tk = self.makeToken(SymMinus, ch) tk = scanner.makeToken(SymMinus, ch)
} }
case '*': case '*':
if next, _ := self.peek(); next == '*' { if next, _ := scanner.peek(); next == '*' {
tk = self.moveOn(SymDoubleStar, ch, next) tk = scanner.moveOn(SymDoubleStar, ch, next)
// } else if next == '/' { // } else if next == '/' {
// tk = self.moveOn(SymClosedComment, ch, next) // tk = self.moveOn(SymClosedComment, ch, next)
} else if next, _ = scanner.peek(); next == '=' {
tk = scanner.moveOn(SymStarEqual, ch, next)
} else { } else {
tk = self.makeToken(SymStar, ch) tk = scanner.makeToken(SymStar, ch)
} }
case '/': case '/':
if next, _ := self.peek(); next == '*' { if next, _ := scanner.peek(); next == '*' {
self.readChar() scanner.readChar()
tk = self.fetchBlockComment() tk = scanner.fetchBlockComment()
} else if next == '/' { } else if next == '/' {
self.readChar() scanner.readChar()
tk = self.fetchOnLineComment() tk = scanner.fetchOnLineComment()
} else { } else {
tk = self.makeToken(SymSlash, ch) tk = scanner.makeToken(SymSlash, ch)
} }
case '\\': case '\\':
if escape { if escape {
tk = self.makeToken(SymBackSlash, ch) tk = scanner.makeToken(SymBackSlash, ch)
escape = false escape = false
} else { } else {
escape = true escape = true
} }
case '|': case '|':
if next, _ := self.peek(); next == '|' { if next, _ := scanner.peek(); next == '|' {
tk = self.moveOn(SymDoubleVertBar, ch, next) tk = scanner.moveOn(SymDoubleVertBar, ch, next)
} else { } else {
tk = self.makeToken(SymVertBar, ch) tk = scanner.makeToken(SymVertBar, ch)
} }
case ',': case ',':
tk = self.makeToken(SymComma, ch) tk = scanner.makeToken(SymComma, ch)
case '^': case '^':
tk = self.makeToken(SymCaret, ch) tk = scanner.makeToken(SymCaret, ch)
case ':': case ':':
if next, _ := self.peek(); next == ':' { if next, _ := scanner.peek(); next == ':' {
tk = self.moveOn(SymDoubleColon, ch, next) tk = scanner.moveOn(SymDoubleColon, ch, next)
} else { } else {
tk = self.makeToken(SymColon, ch) tk = scanner.makeToken(SymColon, ch)
} }
case ';': case ';':
tk = self.makeToken(SymSemiColon, ch) tk = scanner.makeToken(SymSemiColon, ch)
case '.': case '.':
//if next, _ := self.peek(); next >= '0' && next <= '9' { //if next, _ := self.peek(); next >= '0' && next <= '9' {
// tk = self.parseNumber(ch) // tk = self.parseNumber(ch)
//} else if next == '/' { //} else if next == '/' {
if next, _ := self.peek(); next == '/' { if next, _ := scanner.peek(); next == '/' {
tk = self.moveOn(SymDotSlash, ch, next) tk = scanner.moveOn(SymDotSlash, ch, next)
} else if next == '.' { } else if next == '.' {
if next1, _ := self.peek(); next1 == '.' { if next1, _ := scanner.peek(); next1 == '.' {
tk = self.moveOn(SymTripleDot, ch, next, next1) tk = scanner.moveOn(SymTripleDot, ch, next, next1)
} else { } else {
tk = self.moveOn(SymDoubleDot, ch, next) tk = scanner.moveOn(SymDoubleDot, ch, next)
} }
} else { } else {
tk = self.makeToken(SymDot, ch) tk = scanner.makeToken(SymDot, ch)
} }
case '\'': case '\'':
tk = self.makeToken(SymQuote, ch)
case '"':
if escape { if escape {
tk = self.makeToken(SymDoubleQuote, ch) tk = scanner.makeToken(SymQuote, ch)
escape = false escape = false
} else { } else {
tk = self.fetchString() tk = scanner.fetchString(ch)
}
case '"':
if escape {
tk = scanner.makeToken(SymDoubleQuote, ch)
escape = false
} else {
tk = scanner.fetchString(ch)
} }
case '`': case '`':
tk = self.makeToken(SymBackTick, ch) tk = scanner.makeToken(SymBackTick, ch)
case '!': case '!':
if next, _ := self.peek(); next == '=' { if next, _ := scanner.peek(); next == '=' {
tk = self.moveOn(SymNotEqual, ch, next) tk = scanner.moveOn(SymNotEqual, ch, next)
} else { } else {
tk = self.makeToken(SymExclamation, ch) tk = scanner.makeToken(SymExclamation, ch)
} }
case '?': case '?':
if next, _ := self.peek(); next == '?' { if next, _ := scanner.peek(); next == '?' {
tk = self.moveOn(SymDoubleQuestion, ch, next) tk = scanner.moveOn(SymDoubleQuestion, ch, next)
} else if next, _ := self.peek(); next == '=' { } else if next == '=' {
tk = self.moveOn(SymQuestionEqual, ch, next) tk = scanner.moveOn(SymQuestionEqual, ch, next)
} else if next == '!' {
tk = scanner.moveOn(SymQuestionExclam, ch, next)
} else { } else {
tk = self.makeToken(SymQuestion, ch) tk = scanner.makeToken(SymQuestion, ch)
} }
case '&': case '&':
if next, _ := self.peek(); next == '&' { if next, _ := scanner.peek(); next == '&' {
tk = self.moveOn(SymDoubleAmpersand, ch, next) tk = scanner.moveOn(SymDoubleAmpersand, ch, next)
} else { } else {
tk = self.makeToken(SymAmpersand, ch) tk = scanner.makeToken(SymAmpersand, ch)
} }
case '%': case '%':
tk = self.makeToken(SymPercent, ch) tk = scanner.makeToken(SymPercent, ch)
case '#': case '#':
tk = self.makeToken(SymHash, ch) tk = scanner.makeToken(SymHash, ch)
case '@': case '@':
if next, _ := self.peek(); (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z') { if next, _ := scanner.peek(); (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z') {
self.readChar() scanner.readChar()
if tk = self.fetchIdentifier(next); tk.Sym == SymIdentifier { if tk = scanner.fetchIdentifier(next); tk.Sym == SymIdentifier {
//tk.Sym = SymIdRef //tk.Sym = SymIdRef
tk.source = "@" + tk.source tk.source = "@" + tk.source
} else { } else {
tk = self.makeErrorToken(fmt.Errorf("invalid variable reference %q", tk.source)) tk = scanner.makeErrorToken(fmt.Errorf("invalid variable reference %q", tk.source))
} }
} else if next == '@' { } else if next == '@' {
tk = self.moveOn(SymDoubleAt, ch, next) tk = scanner.moveOn(SymDoubleAt, ch, next)
} else { } else {
tk = self.makeToken(SymAt, ch) tk = scanner.makeToken(SymAt, ch)
} }
case '_': case '_':
tk = self.makeToken(SymUndescore, ch) tk = scanner.makeToken(SymUndescore, ch)
case '=': case '=':
if next, _ := self.peek(); next == '=' { if next, _ := scanner.peek(); next == '=' {
tk = self.moveOn(SymDoubleEqual, ch, next) tk = scanner.moveOn(SymDoubleEqual, ch, next)
} else { } else {
tk = self.makeToken(SymEqual, ch) tk = scanner.makeToken(SymEqual, ch)
} }
case '<': case '<':
if next, _ := self.peek(); next == '=' { if next, _ := scanner.peek(); next == '=' {
tk = self.moveOn(SymLessOrEqual, ch, next) tk = scanner.moveOn(SymLessOrEqual, ch, next)
} else if next == '<' { } else if next == '<' {
tk = self.moveOn(SymAppend, ch, next) tk = scanner.moveOn(SymAppend, ch, next)
} else if next == '>' { } else if next == '>' {
tk = self.moveOn(SymLessGreater, ch, next) tk = scanner.moveOn(SymLessGreater, ch, next)
} else { } else {
tk = self.makeToken(SymLess, ch) tk = scanner.makeToken(SymLess, ch)
} }
case '>': case '>':
if next, _ := self.peek(); next == '=' { if next, _ := scanner.peek(); next == '=' {
tk = self.moveOn(SymGreaterOrEqual, ch, next) tk = scanner.moveOn(SymGreaterOrEqual, ch, next)
} else if next == '>' { } else if next == '>' {
tk = self.moveOn(SymInsert, ch, next) tk = scanner.moveOn(SymInsert, ch, next)
} else { } else {
tk = self.makeToken(SymGreater, ch) tk = scanner.makeToken(SymGreater, ch)
} }
case '$': case '$':
if next, _ := self.peek(); next == '(' { if next, _ := scanner.peek(); next == '(' {
tk = self.moveOn(SymDollarRound, ch, next) tk = scanner.moveOn(SymDollarRound, ch, next)
tk.source += ")" tk.source += ")"
} else if next == '$' { } else if next == '$' {
tk = self.moveOn(SymDoubleDollar, ch, next) tk = scanner.moveOn(SymDoubleDollar, ch, next)
} else { } else {
tk = self.makeToken(SymDollar, ch) tk = scanner.makeToken(SymDollar, ch)
} }
case '(': case '(':
if next, _ := self.peek(); next == ')' { // if next, _ := scanner.peek(); next == ')' {
tk = self.moveOn(SymOpenClosedRound, ch, next) // tk = scanner.moveOn(SymOpenClosedRound, ch, next)
} else { // } else {
tk = self.makeToken(SymOpenRound, ch) tk = scanner.makeToken(SymOpenRound, ch)
} // }
case ')': case ')':
tk = self.makeToken(SymClosedRound, ch) tk = scanner.makeToken(SymClosedRound, ch)
case '[': case '[':
tk = self.makeToken(SymOpenSquare, ch) tk = scanner.makeToken(SymOpenSquare, ch)
case ']': case ']':
tk = self.makeToken(SymClosedSquare, ch) tk = scanner.makeToken(SymClosedSquare, ch)
case '{': case '{':
tk = self.makeToken(SymOpenBrace, ch) tk = scanner.makeToken(SymOpenBrace, ch)
case '}': case '}':
tk = self.makeToken(SymClosedBrace, ch) tk = scanner.makeToken(SymClosedBrace, ch)
case '~': case '~':
tk = self.makeToken(SymTilde, ch) tk = scanner.makeToken(SymTilde, ch)
case 0: case 0:
if escape { if escape {
tk = self.makeErrorToken(errors.New("incomplete escape sequence")) tk = scanner.makeErrorToken(errors.New("incomplete escape sequence"))
} }
escape = false escape = false
default: default:
if /*ch == '_' ||*/ (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') { if /*ch == '_' ||*/ (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') {
if tk = self.fetchIdentifier(ch); tk.Sym == SymKwFunc { if tk = scanner.fetchIdentifier(ch); tk.Sym == SymKwFunc {
if next, _ := self.peek(); next == '(' { if next, _ := scanner.peek(); next == '(' {
tk = self.moveOn(SymFuncDef, ch, next) tk = scanner.moveOn(SymFuncDef, ch, next)
} }
} }
} else if ch >= '0' && ch <= '9' { } else if ch >= '0' && ch <= '9' {
tk = self.parseNumber(ch) tk = scanner.parseNumber(ch)
} }
} }
if !escape { if !escape {
@@ -300,14 +325,14 @@ func (self *scanner) fetchNextToken() (tk *Token) {
} }
} }
if tk == nil { if tk == nil {
tk = NewErrorToken(self.row, self.column, fmt.Errorf("unknown symbol '%c'", ch)) tk = NewErrorToken(scanner.row, scanner.column, fmt.Errorf("unknown symbol '%c'", ch))
} }
return return
} }
func (self *scanner) sync(err error) error { func (scanner *scanner) sync(err error) error {
if err == nil { if err == nil {
err = self.unreadChar() err = scanner.unreadChar()
} }
return err return err
} }
@@ -328,32 +353,32 @@ func isHexDigit(ch byte) bool {
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
} }
func (self *scanner) initBase(sb *strings.Builder, currentFirstCh byte) (firstCh byte, numBase int, digitFunc func(byte) bool, err error) { func (scanner *scanner) initBase(currentFirstCh byte) (firstCh byte, numBase int, digitFunc func(byte) bool, err error) {
var ch byte var ch byte
var digitType string var digitType string
firstCh = currentFirstCh firstCh = currentFirstCh
digitFunc = isDecimalDigit digitFunc = isDecimalDigit
numBase = 10 numBase = 10
if ch, err = self.peek(); err == nil { if ch, err = scanner.peek(); err == nil {
if ch == 'b' || ch == 'B' { if ch == 'b' || ch == 'B' {
numBase = 2 numBase = 2
digitType = "binary" digitType = "binary"
self.readChar() scanner.readChar()
digitFunc = isBinaryDigit digitFunc = isBinaryDigit
firstCh, err = self.readChar() firstCh, err = scanner.readChar()
} else if ch == 'o' || ch == 'O' { } else if ch == 'o' || ch == 'O' {
numBase = 8 numBase = 8
digitType = "octal" digitType = "octal"
self.readChar() scanner.readChar()
digitFunc = isOctalDigit digitFunc = isOctalDigit
firstCh, err = self.readChar() firstCh, err = scanner.readChar()
} else if ch == 'x' || ch == 'X' { } else if ch == 'x' || ch == 'X' {
numBase = 16 numBase = 16
digitType = "hex" digitType = "hex"
self.readChar() scanner.readChar()
digitFunc = isHexDigit digitFunc = isHexDigit
firstCh, err = self.readChar() firstCh, err = scanner.readChar()
} }
if err == nil && !digitFunc(firstCh) { if err == nil && !digitFunc(firstCh) {
if len(digitType) == 0 { if len(digitType) == 0 {
@@ -367,7 +392,7 @@ func (self *scanner) initBase(sb *strings.Builder, currentFirstCh byte) (firstCh
return return
} }
func (self *scanner) parseNumber(firstCh byte) (tk *Token) { func (scanner *scanner) parseNumber(firstCh byte) (tk *Token) {
var err error var err error
var ch byte var ch byte
var sym Symbol = SymInteger var sym Symbol = SymInteger
@@ -376,9 +401,9 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
var numBase = 10 var numBase = 10
if firstCh == '0' { if firstCh == '0' {
firstCh, numBase, isDigit, err = self.initBase(&sb, firstCh) firstCh, numBase, isDigit, err = scanner.initBase(firstCh)
} }
for ch = firstCh; err == nil && isDigit(ch); ch, err = self.readChar() { for ch = firstCh; err == nil && isDigit(ch); ch, err = scanner.readChar() {
sb.WriteByte(ch) sb.WriteByte(ch)
} }
@@ -386,9 +411,9 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
if err == nil && ch == '.' { if err == nil && ch == '.' {
sym = SymFloat sym = SymFloat
sb.WriteByte(ch) sb.WriteByte(ch)
ch, err = self.readChar() ch, err = scanner.readChar()
if ch >= '0' && ch <= '9' { if ch >= '0' && ch <= '9' {
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() { for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = scanner.readChar() {
sb.WriteByte(ch) sb.WriteByte(ch)
} }
} }
@@ -397,32 +422,32 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
if ch == 'e' || ch == 'E' { if ch == 'e' || ch == 'E' {
sym = SymFloat sym = SymFloat
sb.WriteByte(ch) sb.WriteByte(ch)
if ch, err = self.readChar(); err == nil { if ch, err = scanner.readChar(); err == nil {
if ch == '+' || ch == '-' { if ch == '+' || ch == '-' {
sb.WriteByte(ch) sb.WriteByte(ch)
ch, err = self.readChar() ch, err = scanner.readChar()
} }
if ch >= '0' && ch <= '9' { if ch >= '0' && ch <= '9' {
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() { for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = scanner.readChar() {
sb.WriteByte(ch) sb.WriteByte(ch)
} }
} else { } else {
err = fmt.Errorf("[%d:%d] expected integer exponent, got %c", self.row, self.column, ch) err = fmt.Errorf("[%d:%d] expected integer exponent, got %c", scanner.row, scanner.column, ch)
} }
} }
} else if ch == '(' { } else if ch == '(' {
sym = SymFraction sym = SymFraction
sb.WriteByte(ch) sb.WriteByte(ch)
ch, err = self.readChar() ch, err = scanner.readChar()
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() { for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = scanner.readChar() {
sb.WriteByte(ch) sb.WriteByte(ch)
} }
if err == nil { if err == nil {
if ch != ')' { if ch != ')' {
err = fmt.Errorf("[%d:%d] expected ')', got '%c'", self.row, self.column, ch) err = fmt.Errorf("[%d:%d] expected ')', got '%c'", scanner.row, scanner.column, ch)
} else { } else {
sb.WriteByte(ch) sb.WriteByte(ch)
_, err = self.readChar() _, err = scanner.readChar()
} }
} }
} }
@@ -430,10 +455,10 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
} }
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
tk = self.makeErrorToken(err) tk = scanner.makeErrorToken(err)
} else { } else {
var value any var value any
err = self.sync(err) err = scanner.sync(err) // TODO: Check this function
txt := sb.String() txt := sb.String()
if sym == SymFloat { if sym == SymFloat {
value, err = strconv.ParseFloat(txt, 64) value, err = strconv.ParseFloat(txt, 64)
@@ -443,39 +468,39 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
value, err = strconv.ParseInt(txt, numBase, 64) value, err = strconv.ParseInt(txt, numBase, 64)
} }
if err == nil { if err == nil {
tk = self.makeValueToken(sym, txt, value) tk = scanner.makeValueToken(sym, txt, value)
} else { } else {
tk = self.makeErrorToken(err) tk = scanner.makeErrorToken(err)
} }
} }
return return
} }
func (self *scanner) fetchIdentifier(firstCh byte) (tk *Token) { func (scanner *scanner) fetchIdentifier(firstCh byte) (tk *Token) {
var err error var err error
var sb strings.Builder var sb strings.Builder
for ch := firstCh; err == nil && (ch == '_' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')); ch, err = self.readChar() { for ch := firstCh; err == nil && (ch == '_' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')); ch, err = scanner.readChar() {
sb.WriteByte(ch) sb.WriteByte(ch)
} }
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
tk = self.makeErrorToken(err) tk = scanner.makeErrorToken(err)
} else if err = self.sync(err); err != nil && err != io.EOF { } else if err = scanner.sync(err); err != nil && err != io.EOF {
tk = self.makeErrorToken(err) tk = scanner.makeErrorToken(err)
} else { } else {
txt := sb.String() txt := sb.String()
uptxt := strings.ToUpper(txt) uptxt := strings.ToUpper(txt)
if sym, ok := keywords[uptxt]; ok { if sym, ok := keywords[uptxt]; ok {
tk = self.makeKeywordToken(sym, uptxt) tk = scanner.makeKeywordToken(sym, uptxt)
} else if uptxt == `TRUE` { } else if uptxt == `TRUE` {
tk = self.makeValueToken(SymBool, txt, true) tk = scanner.makeValueToken(SymBool, txt, true)
} else if uptxt == `FALSE` { } else if uptxt == `FALSE` {
tk = self.makeValueToken(SymBool, txt, false) tk = scanner.makeValueToken(SymBool, txt, false)
} else if ch, _ := self.peek(); ch == '(' { } else if ch, _ := scanner.peek(); ch == '(' {
self.readChar() scanner.readChar()
tk = self.makeValueToken(SymFuncCall, txt+"(", txt) tk = scanner.makeValueToken(SymFuncCall, txt+"(", txt)
} else { } else {
tk = self.makeValueToken(SymIdentifier, txt, txt) tk = scanner.makeValueToken(SymIdentifier, txt, txt)
} }
} }
@@ -495,29 +520,29 @@ func (self *scanner) fetchIdentifier(firstCh byte) (tk *Token) {
return return
} }
func (self *scanner) fetchBlockComment() *Token { func (scanner *scanner) fetchBlockComment() *Token {
return self.fetchUntil(SymComment, false, '*', '/') return scanner.fetchUntil(SymComment, false, '*', '/')
} }
func (self *scanner) fetchOnLineComment() *Token { func (scanner *scanner) fetchOnLineComment() *Token {
return self.fetchUntil(SymComment, true, '\n') return scanner.fetchUntil(SymComment, true, '\n')
} }
func (self *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk *Token) { func (scanner *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk *Token) {
var err error var err error
var ch byte var ch byte
var sb strings.Builder var sb strings.Builder
var value string var value string
ring := NewByteSlider(len(endings)) ring := NewByteSlider(len(endings))
endReached := false endReached := false
for ch, err = self.readChar(); err == nil && !endReached; { for ch, err = scanner.readChar(); err == nil && !endReached; {
sb.WriteByte(ch) sb.WriteByte(ch)
ring.PushEnd(ch) ring.PushEnd(ch)
if ring.Equal(endings) { if ring.Equal(endings) {
value = sb.String()[0 : sb.Len()-len(endings)] value = sb.String()[0 : sb.Len()-len(endings)]
endReached = true endReached = true
} else { } else {
ch, err = self.readChar() ch, err = scanner.readChar()
} }
} }
if !endReached && allowEos { if !endReached && allowEos {
@@ -526,18 +551,18 @@ func (self *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk
} }
if endReached { if endReached {
tk = self.makeValueToken(sym, "", value) tk = scanner.makeValueToken(sym, "", value)
} else { } else {
tk = self.makeErrorToken(err) tk = scanner.makeErrorToken(err)
} }
return return
} }
func (self *scanner) fetchString() (tk *Token) { func (scanner *scanner) fetchString(termCh byte) (tk *Token) {
var err error var err error
var ch, prev byte var ch, prev byte
var sb strings.Builder var sb strings.Builder
for ch, err = self.readChar(); err == nil; ch, err = self.readChar() { for ch, err = scanner.readChar(); err == nil; ch, err = scanner.readChar() {
if prev == '\\' { if prev == '\\' {
switch ch { switch ch {
case '"': case '"':
@@ -554,7 +579,7 @@ func (self *scanner) fetchString() (tk *Token) {
sb.WriteByte(ch) sb.WriteByte(ch)
} }
prev = 0 prev = 0
} else if ch == '"' { } else if ch == termCh {
break break
} else { } else {
prev = ch prev = ch
@@ -565,65 +590,65 @@ func (self *scanner) fetchString() (tk *Token) {
} }
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
tk = self.makeErrorToken(errors.New("missing string termination \"")) tk = scanner.makeErrorToken(errors.New("missing string termination \""))
} else { } else {
tk = self.makeErrorToken(err) tk = scanner.makeErrorToken(err)
} }
} else { } else {
txt := sb.String() txt := sb.String()
tk = self.makeValueToken(SymString, `"`+txt+`"`, txt) tk = scanner.makeValueToken(SymString, `"`+txt+`"`, txt)
} }
return return
} }
func (self *scanner) peek() (next byte, err error) { func (scanner *scanner) peek() (next byte, err error) {
var one []byte var one []byte
if one, err = self.stream.Peek(1); err == nil { if one, err = scanner.stream.Peek(1); err == nil {
next = one[0] next = one[0]
} }
return return
} }
func (self *scanner) skipBlanks() (err error) { func (scanner *scanner) skipBlanks() (err error) {
var one []byte var one []byte
for one, err = self.stream.Peek(1); err == nil && one[0] <= 32; one, err = self.stream.Peek(1) { for one, err = scanner.stream.Peek(1); err == nil && one[0] <= 32; one, err = scanner.stream.Peek(1) {
self.readChar() scanner.readChar()
} }
return return
} }
func (self *scanner) translate(sym Symbol) Symbol { func (scanner *scanner) translate(sym Symbol) Symbol {
if self.translations != nil { if scanner.translations != nil {
if translatedSym, ok := self.translations[sym]; ok { if translatedSym, ok := scanner.translations[sym]; ok {
return translatedSym return translatedSym
} }
} }
return sym return sym
} }
func (self *scanner) moveOn(sym Symbol, chars ...byte) (tk *Token) { func (scanner *scanner) moveOn(sym Symbol, chars ...byte) (tk *Token) {
tk = NewToken(self.row, self.column, self.translate(sym), string(chars)) tk = NewToken(scanner.row, scanner.column, scanner.translate(sym), string(chars))
for i := 1; i < len(chars); i++ { for i := 1; i < len(chars); i++ {
self.readChar() scanner.readChar()
} }
return return
} }
func (self *scanner) makeToken(sym Symbol, chars ...byte) (tk *Token) { func (scanner *scanner) makeToken(sym Symbol, chars ...byte) (tk *Token) {
tk = NewToken(self.row, self.column, self.translate(sym), string(chars)) tk = NewToken(scanner.row, scanner.column, scanner.translate(sym), string(chars))
return return
} }
func (self *scanner) makeKeywordToken(sym Symbol, upperCaseKeyword string) (tk *Token) { func (scanner *scanner) makeKeywordToken(sym Symbol, upperCaseKeyword string) (tk *Token) {
tk = NewToken(self.row, self.column, self.translate(sym), upperCaseKeyword) tk = NewToken(scanner.row, scanner.column, scanner.translate(sym), upperCaseKeyword)
return return
} }
func (self *scanner) makeValueToken(sym Symbol, source string, value any) (tk *Token) { func (scanner *scanner) makeValueToken(sym Symbol, source string, value any) (tk *Token) {
tk = NewValueToken(self.row, self.column, self.translate(sym), source, value) tk = NewValueToken(scanner.row, scanner.column, scanner.translate(sym), source, value)
return return
} }
func (self *scanner) makeErrorToken(err error) *Token { func (scanner *scanner) makeErrorToken(err error) *Token {
return NewErrorToken(self.row, self.column, err) return NewErrorToken(scanner.row, scanner.column, err)
} }
+63 -55
View File
@@ -7,10 +7,11 @@ package expr
import ( import (
"fmt" "fmt"
"slices" "slices"
"strings" // "strings"
) )
type SimpleStore struct { type SimpleStore struct {
parent ExprContext
varStore map[string]any varStore map[string]any
funcStore map[string]ExprFunc funcStore map[string]ExprFunc
} }
@@ -24,87 +25,73 @@ func NewSimpleStore() *SimpleStore {
} }
func filterRefName(name string) bool { return name[0] != '@' } 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
}
func (ctx *SimpleStore) GetParent() ExprContext {
return ctx.parent
}
func (ctx *SimpleStore) Clone() ExprContext { func (ctx *SimpleStore) Clone() ExprContext {
return &SimpleStore{ clone := &SimpleStore{
varStore: CloneFilteredMap(ctx.varStore, filterRefName), varStore: CloneFilteredMap(ctx.varStore, filterRefName),
funcStore: CloneFilteredMap(ctx.funcStore, filterRefName), funcStore: CloneFilteredMap(ctx.funcStore, filterRefName),
} }
return clone
} }
func (ctx *SimpleStore) Merge(src ExprContext) { // func (ctx *SimpleStore) Merge(src ExprContext) {
for _, name := range src.EnumVars(filterRefName) { // for _, name := range src.EnumVars(filterRefName) {
ctx.varStore[name], _ = src.GetVar(name) // ctx.varStore[name], _ = src.GetVar(name)
} // }
for _, name := range src.EnumFuncs(filterRefName) { // for _, name := range src.EnumFuncs(filterRefName) {
ctx.funcStore[name], _ = src.GetFuncInfo(name) // ctx.funcStore[name], _ = src.GetFuncInfo(name)
} // }
// }
func (ctx *SimpleStore) ToString(opt FmtOpt) string {
dict := ctx.ToDict()
return dict.ToString(opt)
} }
func varsCtxToBuilder(sb *strings.Builder, ctx ExprContext, indent int) { func (ctx *SimpleStore) varsToDict(dict *DictType) *DictType {
sb.WriteString("vars: {\n") names := ctx.EnumVars(nil)
first := true slices.Sort(names)
for _, name := range ctx.EnumVars(filterPrivName) { for _, name := range ctx.EnumVars(nil) {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
value, _ := ctx.GetVar(name) value, _ := ctx.GetVar(name)
sb.WriteString(strings.Repeat("\t", indent+1))
sb.WriteString(name)
sb.WriteString(": ")
if f, ok := value.(Formatter); ok { if f, ok := value.(Formatter); ok {
sb.WriteString(f.ToString(0)) (*dict)[name] = f.ToString(0)
} else if _, ok = value.(Functor); ok { } else if _, ok = value.(Functor); ok {
sb.WriteString("func(){}") (*dict)[name] = "func(){}"
// } else if _, ok = value.(map[any]any); ok {
// sb.WriteString("dict{}")
} else { } else {
sb.WriteString(fmt.Sprintf("%v", value)) (*dict)[name] = fmt.Sprintf("%v", value)
} }
} }
sb.WriteString(strings.Repeat("\t", indent)) return dict
sb.WriteString("\n}\n")
} }
func varsCtxToString(ctx ExprContext, indent int) string { func (ctx *SimpleStore) funcsToDict(dict *DictType) *DictType {
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 }) names := ctx.EnumFuncs(func(name string) bool { return true })
slices.Sort(names) slices.Sort(names)
for _, name := range names { for _, name := range names {
if first {
first = false
} else {
sb.WriteByte(',')
sb.WriteByte('\n')
}
value, _ := ctx.GetFuncInfo(name) value, _ := ctx.GetFuncInfo(name)
sb.WriteString(strings.Repeat("\t", indent+1))
if formatter, ok := value.(Formatter); ok { if formatter, ok := value.(Formatter); ok {
sb.WriteString(formatter.ToString(0)) (*dict)[name] = formatter.ToString(0)
} else { } else {
sb.WriteString(fmt.Sprintf("%v", value)) (*dict)[name] = fmt.Sprintf("%v", value)
} }
} }
sb.WriteString("\n}\n") return dict
} }
func (ctx *SimpleStore) ToString(opt FmtOpt) string { func (ctx *SimpleStore) ToDict() (dict *DictType) {
var sb strings.Builder dict = MakeDict()
varsCtxToBuilder(&sb, ctx, 0) (*dict)["variables"] = ctx.varsToDict(MakeDict())
funcsCtxToBuilder(&sb, ctx, 0) (*dict)["functions"] = ctx.funcsToDict(MakeDict())
return sb.String() return
} }
func (ctx *SimpleStore) GetVar(varName string) (v any, exists bool) { func (ctx *SimpleStore) GetVar(varName string) (v any, exists bool) {
@@ -112,6 +99,11 @@ func (ctx *SimpleStore) GetVar(varName string) (v any, exists bool) {
return return
} }
func (ctx *SimpleStore) GetLast() (v any) {
v = ctx.varStore["last"]
return
}
func (ctx *SimpleStore) UnsafeSetVar(varName string, value any) { func (ctx *SimpleStore) UnsafeSetVar(varName string, value any) {
// fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value) // fmt.Printf("[%p] setVar(%v, %v)\n", ctx, varName, value)
ctx.varStore[varName] = value ctx.varStore[varName] = value
@@ -140,6 +132,14 @@ func (ctx *SimpleStore) EnumVars(acceptor func(name string) (accept bool)) (varN
return 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) { func (ctx *SimpleStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
info, exists = ctx.funcStore[name] info, exists = ctx.funcStore[name]
return return
@@ -171,6 +171,14 @@ func (ctx *SimpleStore) EnumFuncs(acceptor func(name string) (accept bool)) (fun
return return
} }
func (ctx *SimpleStore) FuncCount() int {
return len(ctx.funcStore)
}
func (ctx *SimpleStore) DeleteFunc(funcName string) {
delete(ctx.funcStore, funcName)
}
func (ctx *SimpleStore) Call(name string, args []any) (result any, err error) { func (ctx *SimpleStore) Call(name string, args []any) (result any, err error) {
if info, exists := GetLocalFuncInfo(ctx, name); exists { if info, exists := GetLocalFuncInfo(ctx, name); exists {
functor := info.Functor() functor := info.Functor()
+16 -10
View File
@@ -57,16 +57,18 @@ const (
SymTilde // 46: '~' SymTilde // 46: '~'
SymDoubleQuestion // 47: '??' SymDoubleQuestion // 47: '??'
SymQuestionEqual // 48: '?=' SymQuestionEqual // 48: '?='
SymDoubleAt // 49: '@@' SymQuestionExclam // 49: '?!'
SymDoubleColon // 50: '::' SymDoubleAt // 50: '@@'
SymInsert // 51: '>>' SymDoubleColon // 51: '::'
SymAppend // 52: '<<' SymInsert // 52: '>>'
SymCaret // 53: '^' SymAppend // 53: '<<'
SymDollarRound // 54: '$(' SymCaret // 54: '^'
SymOpenClosedRound // 55: '()' SymDollarRound // 55: '$('
SymDoubleDollar // 56: '$$' SymOpenClosedRound // 56: '()'
SymDoubleDot // 57: '..' SymDoubleDollar // 57: '$$'
SymTripleDot // 58: '...' SymDoubleDot // 58: '..'
SymTripleDot // 59: '...'
SymStarEqual // 60: '*='
SymChangeSign SymChangeSign
SymUnchangeSign SymUnchangeSign
SymIdentifier SymIdentifier
@@ -101,9 +103,11 @@ const (
SymKwBut SymKwBut
SymKwFunc SymKwFunc
SymKwBuiltin SymKwBuiltin
SymKwPlugin
SymKwIn SymKwIn
SymKwInclude SymKwInclude
SymKwNil SymKwNil
SymKwUnset
) )
var keywords map[string]Symbol var keywords map[string]Symbol
@@ -113,6 +117,7 @@ func init() {
keywords = map[string]Symbol{ keywords = map[string]Symbol{
"AND": SymKwAnd, "AND": SymKwAnd,
"BUILTIN": SymKwBuiltin, "BUILTIN": SymKwBuiltin,
"PLUGIN": SymKwPlugin,
"BUT": SymKwBut, "BUT": SymKwBut,
"FUNC": SymKwFunc, "FUNC": SymKwFunc,
"IN": SymKwIn, "IN": SymKwIn,
@@ -120,5 +125,6 @@ func init() {
"NOT": SymKwNot, "NOT": SymKwNot,
"OR": SymKwOr, "OR": SymKwOr,
"NIL": SymKwNil, "NIL": SymKwNil,
"UNSET": SymKwUnset,
} }
} }
+1 -1
View File
@@ -47,7 +47,7 @@ func TestAddUnknownTokens(t *testing.T) {
wantErr := errors.New(`unexpected token "%"`) wantErr := errors.New(`unexpected token "%"`)
tree := NewAst() tree := NewAst()
if gotErr := tree.addToken(tk0); gotErr != nil && gotErr.Error() != wantErr.Error() { if _, gotErr := tree.addToken(tk0); gotErr != nil && gotErr.Error() != wantErr.Error() {
t.Errorf("err: got <%v>, want <%v>", gotErr, wantErr) t.Errorf("err: got <%v>, want <%v>", gotErr, wantErr)
} }
} }
+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)
}
+12 -14
View File
@@ -1,16 +1,15 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// t_func-base_test.go // t_builtin-base_test.go
package expr package expr
import ( import (
"errors"
"testing" "testing"
) )
func TestFuncBase(t *testing.T) { func TestFuncBase(t *testing.T) {
section := "Func-Base" section := "Builtin-Base"
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`isNil(nil)`, true, nil}, /* 1 */ {`isNil(nil)`, true, nil},
@@ -21,9 +20,9 @@ func TestFuncBase(t *testing.T) {
/* 6 */ {`int(3.1)`, int64(3), nil}, /* 6 */ {`int(3.1)`, int64(3), nil},
/* 7 */ {`int(3.9)`, int64(3), nil}, /* 7 */ {`int(3.9)`, int64(3), nil},
/* 8 */ {`int("432")`, int64(432), nil}, /* 8 */ {`int("432")`, int64(432), nil},
/* 9 */ {`int("1.5")`, nil, errors.New(`strconv.Atoi: parsing "1.5": invalid syntax`)}, /* 9 */ {`int("1.5")`, nil, `strconv.Atoi: parsing "1.5": invalid syntax`},
/* 10 */ {`int("432", 4)`, nil, errors.New(`int(): too much params -- expected 1, got 2`)}, /* 10 */ {`int("432", 4)`, nil, `int(): too much params -- expected 1, got 2`},
/* 11 */ {`int(nil)`, nil, errors.New(`int(): can't convert nil to int`)}, /* 11 */ {`int(nil)`, nil, `int(): can't convert nil to int`},
/* 12 */ {`isInt(2+1)`, true, nil}, /* 12 */ {`isInt(2+1)`, true, nil},
/* 13 */ {`isInt(3.1)`, false, nil}, /* 13 */ {`isInt(3.1)`, false, nil},
/* 14 */ {`isFloat(3.1)`, true, nil}, /* 14 */ {`isFloat(3.1)`, true, nil},
@@ -42,9 +41,9 @@ func TestFuncBase(t *testing.T) {
/* 27 */ {`dec(2.0)`, float64(2), nil}, /* 27 */ {`dec(2.0)`, float64(2), nil},
/* 28 */ {`dec("2.0")`, float64(2), nil}, /* 28 */ {`dec("2.0")`, float64(2), nil},
/* 29 */ {`dec(true)`, float64(1), nil}, /* 29 */ {`dec(true)`, float64(1), nil},
/* 30 */ {`dec(true")`, nil, errors.New("[1:11] missing string termination \"")}, /* 30 */ {`dec(true")`, nil, `[1:11] missing string termination "`},
/* 31 */ {`dec()`, nil, errors.New(`dec(): too few params -- expected 1, got 0`)}, /* 31 */ {`dec()`, nil, `dec(): too few params -- expected 1, got 0`},
/* 32 */ {`dec(1,2,3)`, nil, errors.New(`dec(): too much params -- expected 1, got 3`)}, /* 32 */ {`dec(1,2,3)`, nil, `dec(): too much params -- expected 1, got 3`},
/* 33 */ {`isBool(false)`, true, nil}, /* 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}, /* 35 */ {`fract(12,2)`, newFraction(6, 1), nil},
@@ -53,15 +52,14 @@ func TestFuncBase(t *testing.T) {
/* 38 */ {`bool(1.0)`, true, nil}, /* 38 */ {`bool(1.0)`, true, nil},
/* 39 */ {`bool("1")`, true, nil}, /* 39 */ {`bool("1")`, true, nil},
/* 40 */ {`bool(false)`, false, 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}, /* 42 */ {`dec(false)`, float64(0), nil},
/* 43 */ {`dec(1|2)`, float64(0.5), nil}, /* 43 */ {`dec(1|2)`, float64(0.5), nil},
/* 44 */ {`dec([1])`, nil, errors.New(`dec(): can't convert list to float`)}, /* 44 */ {`dec([1])`, nil, `dec(): can't convert list to float`},
// /* 45 */ {`string([1])`, nil, errors.New(`string(): can't convert list to string`)}, // /* 45 */ {`string([1])`, nil, `string(): can't convert list to string`},
} }
t.Setenv("EXPR_PATH", ".") t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 2) runTestSuite(t, section, inputs)
parserTest(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(`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")
}
}
+24
View File
@@ -0,0 +1,24 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_builtin-import_test.go
package expr
import (
"testing"
)
func TestFuncImport(t *testing.T) {
section := "Builtin-Import"
inputs := []inputType{
/* 1 */ {`builtin "import"; import("./test-resources/test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 2 */ {`builtin "import"; import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 3 */ {`builtin "import"; importAll("./test-resources/test-funcs.expr"); six()`, int64(6), nil},
/* 4 */ {`builtin "import"; import("./test-resources/sample-export-all.expr"); six()`, int64(6), nil},
}
t.Setenv("EXPR_PATH", "test-resources")
// runTestSuiteSpec(t, section, inputs, 1)
runTestSuite(t, section, inputs)
}
+28
View File
@@ -0,0 +1,28 @@
// 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, `paramter "operator" must be a function, passed <nil> [nil]`},
/* 7 */ {`builtin "iterator"; run($(1,2,3), func(){1}, "prrr")`, nil, `paramter "vars" must be a dictionary, passed prrr [string]`},
}
//t.Setenv("EXPR_PATH", ".")
//runTestSuiteSpec(t, section, inputs, 3)
runTestSuite(t, section, inputs)
}
@@ -1,20 +1,19 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved. // All rights reserved.
// t_func-math-arith_test.go // t_builtin-math-arith_test.go
package expr package expr
import ( import (
"errors"
"testing" "testing"
) )
func TestFuncMathArith(t *testing.T) { func TestFuncMathArith(t *testing.T) {
section := "Func-Math-Arith" section := "Builtin-Math-Arith"
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil}, /* 1 */ {`builtin "math.arith"; add(1,2)`, int64(3), nil},
/* 2 */ {`builtin "math.arith"; add(1,2,3)`, int64(6), 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}, /* 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}, /* 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}, /* 6 */ {`builtin "math.arith"; add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil},
@@ -24,6 +23,6 @@ func TestFuncMathArith(t *testing.T) {
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 69) // runTestSuiteSpec(t, section, inputs, 1)
parserTest(t, section, inputs) runTestSuite(t, section, inputs)
} }
+35
View File
@@ -0,0 +1,35 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_builtin-os-file_test.go
package expr
import (
"testing"
)
func TestFuncOs(t *testing.T) {
section := "Builtin-OS-File"
inputs := []inputType{
/* 1 */ {`builtin "os.file"`, int64(1), nil},
/* 2 */ {`builtin "os.file"; handle=fileOpen("/etc/hosts"); fileClose(handle)`, true, nil},
/* 3 */ {`builtin "os.file"; handle=fileOpen("/etc/hostsX")`, nil, `open /etc/hostsX: no such file or directory`},
/* 4 */ {`builtin "os.file"; handle=fileCreate("/tmp/dummy"); fileClose(handle)`, true, nil},
/* 5 */ {`builtin "os.file"; handle=fileAppend("/tmp/dummy"); fileWriteText(handle, "bye-bye"); fileClose(handle)`, true, nil},
/* 6 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); word=fileReadText(handle, "-"); fileClose(handle);word`, "bye", nil},
/* 7 */ {`builtin "os.file"; word=fileReadText(nil, "-")`, nil, `fileReadText(): invalid file handle`},
/* 8 */ {`builtin "os.file"; fileWriteText(nil, "bye")`, nil, `fileWriteText(): invalid file handle`},
/* 9 */ {`builtin "os.file"; handle=fileOpen()`, nil, `fileOpen(): too few params -- expected 1, got 0`},
/* 10 */ {`builtin "os.file"; handle=fileOpen(123)`, nil, `fileOpen(): missing or invalid file path`},
/* 11 */ {`builtin "os.file"; handle=fileCreate(123)`, nil, `fileCreate(): missing or invalid file path`},
/* 12 */ {`builtin "os.file"; handle=fileAppend(123)`, nil, `fileAppend(): missing or invalid file path`},
/* 13 */ {`builtin "os.file"; handle=fileClose(123)`, nil, `fileClose(): invalid file handle`},
/* 14 */ {`builtin "os.file"; handle=fileOpen("/tmp/dummy"); c=fileReadTextAll(handle); fileClose(handle); c`, "bye-bye", nil},
/* 15 */ {`builtin "os.file"; c=fileReadTextAll(123)`, nil, `fileReadTextAll(): invalid file handle 123 [int64]`},
}
// t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 1)
runTestSuite(t, section, inputs)
}
+68
View File
@@ -0,0 +1,68 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_builtin-string_test.go
package expr
import (
"testing"
)
func TestFuncString(t *testing.T) {
section := "Builtin-String"
inputs := []inputType{
/* 1 */ {`builtin "string"; strJoin("-", "one", "two", "three")`, "one-two-three", nil},
/* 2 */ {`builtin "string"; strJoin("-", ["one", "two", "three"])`, "one-two-three", nil},
/* 3 */ {`builtin "string"; ls= ["one", "two", "three"]; strJoin("-", ls)`, "one-two-three", nil},
/* 4 */ {`builtin "string"; ls= ["one", "two", "three"]; strJoin(1, ls)`, nil, `strJoin(): the "separator" parameter must be a string, got a integer (1)`},
/* 5 */ {`builtin "string"; ls= ["one", 2, "three"]; strJoin("-", ls)`, nil, `strJoin(): expected string, got integer (2)`},
/* 6 */ {`builtin "string"; "<"+strTrim(" bye bye ")+">"`, "<bye bye>", nil},
/* 7 */ {`builtin "string"; strSub("0123456789", 1,2)`, "12", nil},
/* 8 */ {`builtin "string"; strSub("0123456789", -3,2)`, "78", nil},
/* 9 */ {`builtin "string"; strSub("0123456789", -3)`, "789", nil},
/* 10 */ {`builtin "string"; strSub("0123456789")`, "0123456789", nil},
/* 11 */ {`builtin "string"; strStartsWith("0123456789", "xyz", "012")`, true, nil},
/* 12 */ {`builtin "string"; strStartsWith("0123456789", "xyz", "0125")`, false, nil},
/* 13 */ {`builtin "string"; strStartsWith("0123456789")`, nil, `strStartsWith(): too few params -- expected 2 or more, got 1`},
/* 14 */ {`builtin "string"; strEndsWith("0123456789", "xyz", "789")`, true, nil},
/* 15 */ {`builtin "string"; strEndsWith("0123456789", "xyz", "0125")`, false, nil},
/* 16 */ {`builtin "string"; strEndsWith("0123456789")`, nil, `strEndsWith(): too few params -- expected 2 or more, got 1`},
/* 17 */ {`builtin "string"; strSplit("one-two-three", "-")`, newListA("one", "two", "three"), nil},
/* 18 */ {`builtin "string"; strJoin("-", [1, "two", "three"])`, nil, `strJoin(): expected string, got integer (1)`},
/* 19 */ {`builtin "string"; strJoin()`, nil, `strJoin(): too few params -- expected 1 or more, got 0`},
/* 69 */ /*{`builtin "string"; $$global`, `vars: {
}
funcs: {
add(any=0 ...) -> number,
dec(any) -> decimal,
endsWithStr(source, suffix) -> boolean,
fract(any, denominator=1) -> fraction,
import( ...) -> any,
importAll( ...) -> any,
int(any) -> integer,
isBool(any) -> boolean,
isDec(any) -> boolean,
isDict(any) -> boolean,
isFloat(any) -> boolean,
isFract(any) -> boolean,
isInt(any) -> boolean,
isList(any) -> boolean,
isNil(any) -> boolean,
isString(any) -> boolean,
joinStr(separator, item="" ...) -> string,
mul(any=1 ...) -> number,
splitStr(source, separator="", count=-1) -> list of string,
startsWithStr(source, prefix) -> boolean,
subStr(source, start=0, count=-1) -> string,
trimStr(source) -> string
}
`, nil},*/
}
//t.Setenv("EXPR_PATH", ".")
// runTestSuiteSpec(t, section, inputs, 19)
runTestSuite(t, section, inputs)
}
+113
View File
@@ -0,0 +1,113 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_common_test.go
package expr
import (
"errors"
"reflect"
"strings"
"testing"
)
type inputType struct {
source string
wantResult any
wantErr any
}
func runCtxTestSuiteSpec(t *testing.T, ctx ExprContext, section string, inputs []inputType, spec ...int) {
succeeded := 0
failed := 0
for _, count := range spec {
good := doTest(t, ctx, section, &inputs[count-1], count)
if good {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(spec), succeeded, failed)
}
func runTestSuiteSpec(t *testing.T, section string, inputs []inputType, spec ...int) {
runCtxTestSuiteSpec(t, nil, section, inputs, spec...)
}
func runCtxTestSuite(t *testing.T, ctx ExprContext, section string, inputs []inputType) {
succeeded := 0
failed := 0
for i, input := range inputs {
good := doTest(t, ctx, section, &input, i+1)
if good {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
}
func runTestSuite(t *testing.T, section string, inputs []inputType) {
runCtxTestSuite(t, nil, section, inputs)
}
func getWantedError(input *inputType) error {
var wantErr error
var ok bool
if wantErr, ok = input.wantErr.(error); !ok {
if msg, ok := input.wantErr.(string); ok {
wantErr = errors.New(msg)
}
}
return wantErr
}
func doTest(t *testing.T, ctx ExprContext, section string, input *inputType, count int) (good bool) {
var expr Expr
var gotResult any
var gotErr error
wantErr := getWantedError(input)
parser := NewParser()
if ctx == nil {
ctx = NewSimpleStore()
}
logTest(t, count, section, input.source, input.wantResult, wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good = true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
eq := reflect.DeepEqual(gotResult, input.wantResult)
if !eq /*gotResult != input.wantResult*/ {
t.Errorf("%d: %q -> result = %v [%s], want = %v [%s]", count, input.source, gotResult, TypeName(gotResult), input.wantResult, TypeName(input.wantResult))
good = false
}
if gotErr != wantErr {
if wantErr == nil || gotErr == nil || (gotErr.Error() != wantErr.Error()) {
t.Errorf("%d: %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 -- %q --> %v", section, n, source, wantResult)
} else {
t.Logf("[-]%s nr %3d -- %q --> %v", section, n, source, wantErr)
}
}
+10 -4
View File
@@ -22,7 +22,7 @@ func TestDictParser(t *testing.T) {
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`{}`, map[any]any{}, nil}, /* 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}, /* 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}, /* 4 */ {`{1:"one",2:"two",3:"three"}[3]`, "three", nil},
/* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil}, /* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil},
@@ -31,7 +31,13 @@ func TestDictParser(t *testing.T) {
/* 8 */ {`D={"a":1, "b":2}; D["a"]=9; D`, map[any]any{"a": 9, "b": 2}, nil}, /* 8 */ {`D={"a":1, "b":2}; D["a"]=9; D`, map[any]any{"a": 9, "b": 2}, nil},
/* 9 */ {`D={"a":1, "b":2}; D["z"]=9; D`, map[any]any{"z": 9, "a": 1, "b": 2}, nil}, /* 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`)}, /* 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 succeeded := 0
failed := 0 failed := 0
@@ -49,7 +55,7 @@ func TestDictParser(t *testing.T) {
ctx.SetVar("var1", int64(123)) ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc") ctx.SetVar("var2", "abc")
ImportMathFuncs(ctx) ImportMathFuncs(ctx)
parser := NewParser(ctx) parser := NewParser()
logTest(t, i+1, "Dict", input.source, input.wantResult, input.wantErr) logTest(t, i+1, "Dict", input.source, input.wantResult, input.wantErr)
@@ -110,7 +116,7 @@ func TestDictToStringMultiLine(t *testing.T) {
var good bool var good bool
section := "dict-ToString-ML" section := "dict-ToString-ML"
want := `{ want := `{
"first": 1 "first": 1
}` }`
args := map[any]*term{ args := map[any]*term{
"first": newLiteralTerm(NewValueToken(0, 0, SymInteger, "1", 1)), "first": newLiteralTerm(NewValueToken(0, 0, SymInteger, "1", 1)),
+17 -6
View File
@@ -14,13 +14,24 @@ func TestExpr(t *testing.T) {
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`0?{}`, nil, nil}, /* 1 */ {`0?{}`, nil, nil},
/* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil}, /* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
/* 3 */ {`builtin "os.file"; f=openFile("test-file.txt"); line=readFile(f); closeFile(f); line`, "uno", nil}, /* 3 */ {`builtin "os.file"; f=fileOpen("test-file.txt"); line=fileReadText(f); fileClose(f); line`, "uno", nil},
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil}, /* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
/* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil}, /* 5 */ {`1 ? {1} : [1+0] {3*(1+1)}`, int64(6), nil},
/* 6 */ {` /* 6 */ {`a=3; a+=1`, int64(4), nil},
/* 7 */ {`a=3; a-=1`, int64(2), nil},
/* 8 */ {`a=3; a*=2`, 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`, nil, `[1:8] left operand of "=" must be a variable or a collection's item`},
/* 12 */ {`*=2`, nil, `[1:2] left operand of "*=" must be a variable or a variable expression`},
/* 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 */ {`
ds={ ds={
"init":func(end){@end=end; @current=0 but true}, "init":func(@end){@current=0 but true},
"current":func(){current}, //"current":func(){current},
"next":func(){ "next":func(){
((next=current+1) <= end) ? [true] {@current=next but current} :: {nil} ((next=current+1) <= end) ? [true] {@current=next but current} :: {nil}
} }
@@ -32,6 +43,6 @@ func TestExpr(t *testing.T) {
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 3) //runTestSuiteSpec(t, section, inputs, 9)
parserTest(t, section, inputs) runTestSuite(t, section, inputs)
} }
+9 -9
View File
@@ -5,7 +5,6 @@
package expr package expr
import ( import (
"errors"
"testing" "testing"
) )
@@ -18,23 +17,24 @@ func TestFractionsParser(t *testing.T) {
/* 4 */ {`1|2 * 1`, newFraction(1, 2), nil}, /* 4 */ {`1|2 * 1`, newFraction(1, 2), nil},
/* 5 */ {`1|2 * 2|3`, newFraction(2, 6), nil}, /* 5 */ {`1|2 * 2|3`, newFraction(2, 6), nil},
/* 6 */ {`1|2 / 2|3`, newFraction(3, 4), nil}, /* 6 */ {`1|2 / 2|3`, newFraction(3, 4), nil},
/* 7 */ {`1|"5"`, nil, errors.New(`denominator must be integer, got string (5)`)}, /* 7 */ {`1|"5"`, nil, `denominator must be integer, got string (5)`},
/* 8 */ {`"1"|5`, nil, errors.New(`numerator must be integer, got string (1)`)}, /* 8 */ {`"1"|5`, nil, `numerator must be integer, got string (1)`},
/* 9 */ {`1|+5`, nil, errors.New(`[1:3] infix operator "|" requires two non-nil operands, got 1`)}, /* 9 */ {`1|+5`, nil, `[1:3] infix operator "|" requires two non-nil operands, got 1`},
/* 10 */ {`1|(-2)`, newFraction(-1, 2), nil}, /* 10 */ {`1|(-2)`, newFraction(-1, 2), nil},
/* 11 */ {`builtin "math.arith"; add(1|2, 2|3)`, newFraction(7, 6), 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}, /* 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}, /* 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}, /* 14 */ {`builtin "math.arith"; mul(1|2, 1.0, 2)`, float64(1.0), nil},
/* 15 */ {`1|0`, nil, errors.New(`division by zero`)}, /* 15 */ {`1|0`, nil, `division by zero`},
/* 16 */ {`fract(-0.5)`, newFraction(-1, 2), nil}, /* 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}, /* 18 */ {`fract("-1")`, newFraction(-1, 1), nil},
/* 19 */ {`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`)}, /* 20 */ {`fract("1a")`, (*FractionType)(nil), `strconv.ParseInt: parsing "1a": invalid syntax`},
/* 21 */ {`fract(1,0)`, nil, errors.New(`fract(): division by zero`)}, /* 21 */ {`fract(1,0)`, nil, `fract(): division by zero`},
/* 22 */ {`string(1|2)`, "1|2", nil},
} }
parserTest(t, section, inputs) runTestSuite(t, section, inputs)
} }
func TestFractionToStringSimple(t *testing.T) { func TestFractionToStringSimple(t *testing.T) {
-24
View File
@@ -1,24 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_func-import_test.go
package expr
import (
"testing"
)
func TestFuncImport(t *testing.T) {
section := "Func-Import"
inputs := []inputType{
/* 1 */ {`builtin "import"; import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 2 */ {`builtin "import"; import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 3 */ {`builtin "import"; importAll("./test-funcs.expr"); six()`, int64(6), nil},
/* 4 */ {`builtin "import"; import("./sample-export-all.expr"); six()`, int64(6), nil},
}
t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 69)
parserTest(t, section, inputs)
}
-29
View File
@@ -1,29 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_func-os_test.go
package expr
import (
"errors"
"testing"
)
func TestFuncOs(t *testing.T) {
section := "Func-OS"
inputs := []inputType{
/* 1 */ {`builtin "os.file"`, int64(1), nil},
/* 2 */ {`builtin "os.file"; handle=openFile("/etc/hosts"); closeFile(handle)`, true, nil},
/* 3 */ {`builtin "os.file"; handle=openFile("/etc/hostsX")`, nil, errors.New(`open /etc/hostsX: no such file or directory`)},
/* 4 */ {`builtin "os.file"; handle=createFile("/tmp/dummy"); closeFile(handle)`, true, nil},
/* 5 */ {`builtin "os.file"; handle=appendFile("/tmp/dummy"); writeFile(handle, "bye-bye"); closeFile(handle)`, true, nil},
/* 6 */ {`builtin "os.file"; handle=openFile("/tmp/dummy"); word=readFile(handle, "-"); closeFile(handle);word`, "bye", nil},
/* 7 */ {`builtin "os.file"; word=readFile(nil, "-")`, nil, errors.New(`readFileFunc(): invalid file handle`)},
/* 7 */ {`builtin "os.file"; writeFile(nil, "bye")`, nil, errors.New(`writeFileFunc(): invalid file handle`)},
}
// t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 69)
parserTest(t, section, inputs)
}
-69
View File
@@ -1,69 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_func-string_test.go
package expr
import (
"errors"
"testing"
)
func TestFuncString(t *testing.T) {
section := "Func-String"
inputs := []inputType{
/* 1 */ {`builtin "string"; joinStr("-", "one", "two", "three")`, "one-two-three", nil},
/* 2 */ {`builtin "string"; joinStr("-", ["one", "two", "three"])`, "one-two-three", nil},
/* 3 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr("-", ls)`, "one-two-three", nil},
/* 4 */ {`builtin "string"; ls= ["one", "two", "three"]; joinStr(1, ls)`, nil, errors.New(`joinStr() the "separator" parameter must be a string, got a integer (1)`)},
/* 5 */ {`builtin "string"; ls= ["one", 2, "three"]; joinStr("-", ls)`, nil, errors.New(`joinStr() expected string, got integer (2)`)},
/* 6 */ {`builtin "string"; "<"+trimStr(" bye bye ")+">"`, "<bye bye>", nil},
/* 7 */ {`builtin "string"; subStr("0123456789", 1,2)`, "12", nil},
/* 8 */ {`builtin "string"; subStr("0123456789", -3,2)`, "78", nil},
/* 9 */ {`builtin "string"; subStr("0123456789", -3)`, "789", nil},
/* 10 */ {`builtin "string"; subStr("0123456789")`, "0123456789", nil},
/* 11 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "012")`, true, nil},
/* 12 */ {`builtin "string"; startsWithStr("0123456789", "xyz", "0125")`, false, nil},
/* 13 */ {`builtin "string"; startsWithStr("0123456789")`, nil, errors.New(`startsWithStr(): too few params -- expected 2 or more, got 1`)},
/* 14 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "789")`, true, nil},
/* 15 */ {`builtin "string"; endsWithStr("0123456789", "xyz", "0125")`, false, nil},
/* 16 */ {`builtin "string"; endsWithStr("0123456789")`, nil, errors.New(`endsWithStr(): too few params -- expected 2 or more, got 1`)},
/* 17 */ {`builtin "string"; splitStr("one-two-three", "-")`, newListA("one", "two", "three"), nil},
/* 18 */ {`builtin "string"; joinStr("-", [1, "two", "three"])`, nil, errors.New(`joinStr() expected string, got integer (1)`)},
/* 19 */ {`builtin "string"; joinStr()`, nil, errors.New(`joinStr(): too few params -- expected 1 or more, got 0`)},
/* 69 */ /*{`builtin "string"; $$global`, `vars: {
}
funcs: {
add(any=0 ...) -> number,
dec(any) -> decimal,
endsWithStr(source, suffix) -> boolean,
fract(any, denominator=1) -> fraction,
import( ...) -> any,
importAll( ...) -> any,
int(any) -> integer,
isBool(any) -> boolean,
isDec(any) -> boolean,
isDict(any) -> boolean,
isFloat(any) -> boolean,
isFract(any) -> boolean,
isInt(any) -> boolean,
isList(any) -> boolean,
isNil(any) -> boolean,
isString(any) -> boolean,
joinStr(separator, item="" ...) -> string,
mul(any=1 ...) -> number,
splitStr(source, separator="", count=-1) -> list of string,
startsWithStr(source, prefix) -> boolean,
subStr(source, start=0, count=-1) -> string,
trimStr(source) -> string
}
`, nil},*/
}
//t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 19)
parserTest(t, section, inputs)
}
+15 -11
View File
@@ -5,7 +5,6 @@
package expr package expr
import ( import (
"errors"
"testing" "testing"
) )
@@ -17,25 +16,29 @@ func TestFuncs(t *testing.T) {
/* 3 */ {`double=func(x){2*x}; double(3)`, int64(6), nil}, /* 3 */ {`double=func(x){2*x}; double(3)`, int64(6), nil},
/* 4 */ {`double=func(x){2*x}; a=5; double(3+a) + 1`, int64(17), nil}, /* 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}, /* 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}, /* 7 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
/* 8 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), 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}, /* 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}, /* 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}, /* 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}, /* 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 much params -- expected 0, got 1`},
/* 15 */ {`f=func(x,n=2){x+n}; f(3)`, int64(5), nil}, /* 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`)}, /* 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, errors.New(`[1:24] expected "function-param-value", got ")"`)}, /* 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 */ {`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 ")"`)}, // /* 18 */ {`f=func(a){a*2}`, nil, errors.New(`[1:24] expected "function-param-value", got ")"`)},
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 17) //runTestSuiteSpec(t, section, inputs, 20)
parserTest(t, section, inputs) runTestSuite(t, section, inputs)
} }
func dummy(ctx ExprContext, name string, args []any) (result any, err error) { func dummy(ctx ExprContext, name string, args []any) (result any, err error) {
@@ -43,16 +46,17 @@ func dummy(ctx ExprContext, name string, args []any) (result any, err error) {
} }
func TestFunctionToStringSimple(t *testing.T) { func TestFunctionToStringSimple(t *testing.T) {
source := newGolangFunctor(dummy) source := NewGolangFunctor(dummy)
want := "func() {<body>}" want := "func(){}"
got := source.ToString(0) got := source.ToString(0)
if got != want { if got != want {
t.Errorf(`(func() -> result = %v [%T], want = %v [%T]`, got, got, want, want) t.Errorf(`(func() -> result = %v [%T], want = %v [%T]`, got, got, want, want)
} }
} }
func TestFunctionGetFunc(t *testing.T) { func TestFunctionGetFunc(t *testing.T) {
source := newGolangFunctor(dummy) source := NewGolangFunctor(dummy)
want := ExprFunc(nil) want := ExprFunc(nil)
got := source.GetFunc() got := source.GetFunc()
if got != want { if got != want {
+2 -3
View File
@@ -5,7 +5,6 @@
package expr package expr
import ( import (
"errors"
"testing" "testing"
) )
@@ -17,11 +16,11 @@ func TestCollections(t *testing.T) {
/* 3 */ {`"abcdef"[1:]`, "bcdef", nil}, /* 3 */ {`"abcdef"[1:]`, "bcdef", nil},
/* 4 */ {`"abcdef"[:]`, "abcdef", nil}, /* 4 */ {`"abcdef"[:]`, "abcdef", nil},
// /* 5 */ {`[0,1,2,3,4][:]`, ListType{int64(0), int64(1), int64(2), int64(3), int64(4)}, 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] left operand '(1, 2)' [pair] and right operand '3' [integer] are not compatible with operator ":"`},
} }
t.Setenv("EXPR_PATH", ".") t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 5) // parserTestSpec(t, section, inputs, 5)
parserTest(t, section, inputs) runTestSuite(t, section, inputs)
} }
+162
View File
@@ -0,0 +1,162 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// t_iter-list_test.go
package expr
import (
"io"
"testing"
)
func TestNewListIterator(t *testing.T) {
list := newListA("a", "b", "c", "d")
it := NewListIterator(list, []any{1, 3, 1})
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "b" {
t.Errorf("expected %q, got %q", "b", item)
} else {
t.Logf("Next: %v", item)
}
}
func TestNewListIterator2(t *testing.T) {
list := newListA("a", "b", "c", "d")
it := NewListIterator(list, []any{3, 1, -1})
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "d" {
t.Errorf("expected %q, got %q", "d", item)
} else {
t.Logf("Next: %v", item)
}
}
func TestNewListIterator3(t *testing.T) {
list := newListA("a", "b", "c", "d")
it := NewListIterator(list, []any{1, -1, 1})
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "b" {
t.Errorf("expected %q, got %q", "b", item)
} else {
t.Logf("Next: %v", item)
}
}
func TestNewIterList2(t *testing.T) {
list := []any{"a", "b", "c", "d"}
it := NewArrayIterator(list)
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "a" {
t.Errorf("expected %q, got %q", "a", item)
} else {
t.Logf("Next: %v", item)
}
}
func TestNewIterList3(t *testing.T) {
list := []any{"a", "b", "c", "d"}
it := NewAnyIterator(list)
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "a" {
t.Errorf("expected %q, got %q", "a", item)
} else {
t.Logf("Next: %v", item)
}
}
func TestNewIterList4(t *testing.T) {
list := any(nil)
it := NewAnyIterator(list)
if _, err := it.Next(); err != io.EOF {
t.Errorf("error: %v", err)
}
}
func TestNewIterList5(t *testing.T) {
list := "123"
it := NewAnyIterator(list)
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "123" {
t.Errorf("expected %q, got %q", "123", item)
} else {
t.Logf("Next: %v", item)
}
}
func TestNewIterList6(t *testing.T) {
list := newListA("a", "b", "c", "d")
it1 := NewAnyIterator(list)
it := NewAnyIterator(it1)
if item, err := it.Next(); err != nil {
t.Errorf("error: %v", err)
} else if item != "a" {
t.Errorf("expected %q, got %q", "a", item)
} else {
t.Logf("Next: %v", item)
}
}
func TestNewString(t *testing.T) {
list := "123"
it := NewAnyIterator(list)
if s := it.String(); s != "$(#1)" {
t.Errorf("expected $(#1), got %s", s)
}
}
func TestHasOperation(t *testing.T) {
list := newListA("a", "b", "c", "d")
it := NewListIterator(list, []any{1, 3, 1})
hasOp := it.HasOperation("reset")
if !hasOp {
t.Errorf("HasOperation(reset) must be true, got false")
}
}
func TestCallOperationReset(t *testing.T) {
list := newListA("a", "b", "c", "d")
it := NewListIterator(list, []any{1, 3, 1})
if v, err := it.CallOperation("reset", nil); err != nil {
t.Errorf("Error on CallOperation(reset): %v", err)
} else {
t.Logf("Reset result: %v", v)
}
}
func TestCallOperationIndex(t *testing.T) {
list := newListA("a", "b", "c", "d")
it := NewListIterator(list, []any{1, 3, 1})
if v, err := it.CallOperation("index", nil); err != nil {
t.Errorf("Error on CallOperation(index): %v", err)
} else {
t.Logf("Index result: %v", v)
}
}
func TestCallOperationCount(t *testing.T) {
list := newListA("a", "b", "c", "d")
it := NewListIterator(list, []any{1, 3, 1})
if v, err := it.CallOperation("count", nil); err != nil {
t.Errorf("Error on CallOperation(count): %v", err)
} else {
t.Logf("Count result: %v", v)
}
}
func TestCallOperationUnknown(t *testing.T) {
list := newListA("a", "b", "c", "d")
it := NewListIterator(list, []any{1, 3, 1})
if v, err := it.CallOperation("unknown", nil); err == nil {
t.Errorf("Expected error on CallOperation(unknown), got %v", v)
}
}
+20 -14
View File
@@ -7,20 +7,26 @@ package expr
import "testing" import "testing"
func TestIteratorParser(t *testing.T) { func TestIteratorParser(t *testing.T) {
section := "Iterator"
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`include "iterator.expr"; it=$(ds,3); ()it`, int64(0), nil}, /* 1 */ {`include "test-resources/iterator.expr"; it=$(ds,3); ^it`, int64(0), nil},
/* 2 */ {`include "iterator.expr"; it=$(ds,3); it++; it++`, int64(1), nil}, /* 2 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++`, int64(1), nil},
/* 3 */ {`include "iterator.expr"; it=$(ds,3); it++; it++; #it`, int64(2), nil}, /* 3 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; #it`, int64(3), nil},
/* 4 */ {`include "iterator.expr"; it=$(ds,3); it++; it++; it.reset; ()it`, int64(0), nil}, /* 4 */ {`include "test-resources/iterator.expr"; it=$(ds,3); it++; it++; it.reset; ^it`, int64(0), nil},
/* 5 */ {`builtin "math.arith"; include "iterator.expr"; it=$(ds,3); add(it)`, int64(6), nil}, /* 5 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); add(it)`, int64(6), nil},
/* 6 */ {`builtin "math.arith"; include "iterator.expr"; it=$(ds,3); mul(it)`, int64(0), nil}, /* 6 */ {`builtin "math.arith"; include "test-resources/iterator.expr"; it=$(ds,3); mul(it)`, int64(0), nil},
/* 7 */ {`builtin "math.arith"; include "file-reader.expr"; it=$(ds,"int.list"); mul(it)`, int64(12000), nil}, /* 7 */ {`builtin "math.arith"; include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); mul(it)`, int64(12000), nil},
/* 8 */ {`include "file-reader.expr"; it=$(ds,"int.list"); it++; it.index`, int64(0), nil}, /* 8 */ {`include "test-resources/file-reader.expr"; it=$(ds,"test-resources/int.list"); it++; it.index`, int64(0), nil},
/* 10 */ {`include "file-reader.expr"; it=$(ds,"int.list"); it.clean`, true, nil}, /* 9 */ {`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}, /* 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},
} }
// inputs1 := []inputType{
// /* 1 */ {`0?{}`, nil, nil}, //runTestSuiteSpec(t, section, inputs, 3)
// } runTestSuite(t, section, inputs)
parserTest(t, "Iterator", inputs)
} }
+16 -92
View File
@@ -5,20 +5,12 @@
package expr package expr
import ( import (
"errors"
"strings"
"testing" "testing"
) )
func TestListParser(t *testing.T) { func TestListParser(t *testing.T) {
section := "List" section := "List"
/* type inputType struct {
source string
wantResult any
wantErr error
}
*/
inputs := []inputType{ inputs := []inputType{
/* 1 */ {`[]`, newListA(), nil}, /* 1 */ {`[]`, newListA(), nil},
/* 2 */ {`[1,2,3]`, newListA(int64(1), int64(2), int64(3)), nil}, /* 2 */ {`[1,2,3]`, newListA(int64(1), int64(2), int64(3)), nil},
@@ -29,14 +21,14 @@ func TestListParser(t *testing.T) {
/* 7 */ {`add([1,4,3,2])`, int64(10), nil}, /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
/* 8 */ {`add([1,[2,2],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}, /* 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}, /* 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}, /* 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}, /* 13 */ {`2-1 >> [2,3]`, newListA(int64(1), int64(2), int64(3)), nil},
/* 14 */ {`[1,2,3][1]`, int64(2), nil}, /* 14 */ {`[1,2,3][1]`, int64(2), nil},
/* 15 */ {`ls=[1,2,3] but ls[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}, /* 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}, /* 18 */ {`["a", "b", "c"]`, newListA("a", "b", "c"), nil},
/* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil}, /* 19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil},
/* 20 */ {`#["a", "b", "c"]`, int64(3), nil}, /* 20 */ {`#["a", "b", "c"]`, int64(3), nil},
@@ -44,94 +36,26 @@ func TestListParser(t *testing.T) {
/* 22 */ {`a=[1,2]; (a)<<3`, newListA(int64(1), int64(2), int64(3)), nil}, /* 22 */ {`a=[1,2]; (a)<<3`, newListA(int64(1), int64(2), int64(3)), nil},
/* 23 */ {`a=[1,2]; (a)<<3; a`, newListA(int64(1), int64(2)), nil}, /* 23 */ {`a=[1,2]; (a)<<3; a`, newListA(int64(1), int64(2)), nil},
/* 24 */ {`["a","b","c","d"][1]`, "b", 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}, /* 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`)}, /* 27 */ {`["a", "b", "c"] << ;`, nil, `[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 "<<"`)}, /* 28 */ {`2 << 3;`, nil, `[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`)}, /* 29 */ {`but >> ["a", "b", "c"]`, nil, `[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 ">>"`)}, /* 30 */ {`2 >> 3;`, nil, `[1:4] left operand '2' [integer] and right operand '3' [integer] are not compatible with operator ">>"`},
/* 31 */ {`a=[1,2]; a<<3`, newListA(int64(1), int64(2), int64(3)), nil}, /* 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}, /* 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}, /* 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)`)}, /* 35 */ {`L=[1,2]; L[5]=9; L`, nil, `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]`)}, /* 36 */ {`L=[1,2]; L[]=9; L`, nil, `[1:12] index/key specification expected, got [] [list]`},
/* 37 */ {`L=[1,2]; L[nil]=9;`, nil, errors.New(`[1:12] index/key is nil`)}, /* 37 */ {`L=[1,2]; L[nil]=9;`, nil, `[1:12] index/key is nil`},
// /* 8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, nil}, /* 38 */ {`[0,1,2,3,4][2:3]`, newListA(int64(2)), nil},
// /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil}, /* 39 */ {`[0,1,2,3,4][3:-1]`, newListA(int64(3)), nil},
/* 40 */ {`[0,1,2,3,4][-3:-1]`, newListA(int64(2), int64(3)), nil},
/* 41 */ {`[0,1,2,3,4][0:]`, newListA(int64(0), int64(1), int64(2), int64(3), int64(4)), nil},
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 17) // runTestSuiteSpec(t, section, inputs, 1)
parserTest(t, section, inputs) runTestSuite(t, section, inputs)
return
succeeded := 0
failed := 0
// inputs1 := []inputType{
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
// }
for i, input := range inputs {
var expr *ast
var gotResult any
var gotErr error
ctx := NewSimpleStore()
ImportMathFuncs(ctx)
parser := NewParser(ctx)
logTest(t, i+1, "List", input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good := true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
if (gotResult == nil && input.wantResult != nil) || (gotResult != nil && input.wantResult == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotList, okGot := gotResult.([]any); okGot {
if wantList, okWant := input.wantResult.([]any); okWant {
if (gotList == nil && wantList != nil) || (gotList != nil && wantList == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
} else {
equal := len(gotList) == len(wantList)
if equal {
for i, gotItem := range gotList {
wantItem := wantList[i]
equal = gotItem == wantItem
if !equal {
break
}
}
}
if !equal {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
}
}
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
good = false
}
}
if good {
succeeded++
} else {
failed++
}
}
t.Logf("%s -- test count: %d, succeeded: %d, failed: %d", section, len(inputs), succeeded, failed)
} }
+1 -1
View File
@@ -12,7 +12,7 @@ func TestIterateModules(t *testing.T) {
section := "Module-Register" section := "Module-Register"
mods := make([]string, 0, 100) mods := make([]string, 0, 100)
IterateModules(func(name, description string, imported bool) bool { IterateBuiltinModules(func(name, description string, imported bool) bool {
mods = append(mods, name) mods = append(mods, name)
return true return true
}) })
+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)
}
+37 -113
View File
@@ -5,18 +5,9 @@
package expr package expr
import ( import (
"errors"
"reflect"
"strings"
"testing" "testing"
) )
type inputType struct {
source string
wantResult any
wantErr error
}
func TestGeneralParser(t *testing.T) { func TestGeneralParser(t *testing.T) {
section := "Parser" section := "Parser"
@@ -57,13 +48,13 @@ func TestGeneralParser(t *testing.T) {
/* 34 */ {`(((1)))`, int64(1), nil}, /* 34 */ {`(((1)))`, int64(1), nil},
/* 35 */ {`var2="abc"; "uno_" + var2`, `uno_abc`, nil}, /* 35 */ {`var2="abc"; "uno_" + var2`, `uno_abc`, nil},
/* 36 */ {`0 || 0.0 && "hello"`, false, 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 "+"`)}, /* 37 */ {`"s" + true`, nil, `[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]`)}, /* 38 */ {`+false`, nil, `[1:2] prefix/postfix operator "+" do not support operand 'false' [bool]`},
/* 39 */ {`false // very simple expression`, false, nil}, /* 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}, /* 41 */ {"", nil, nil},
/* 42 */ {"4!", int64(24), 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}, /* 44 */ {"-4!", int64(-24), nil},
/* 45 */ {"1.5 < 7", true, nil}, /* 45 */ {"1.5 < 7", true, nil},
/* 46 */ {"1.5 > 7", false, nil}, /* 46 */ {"1.5 > 7", false, nil},
@@ -75,20 +66,20 @@ func TestGeneralParser(t *testing.T) {
/* 52 */ {`"1.5" > "7"`, false, nil}, /* 52 */ {`"1.5" > "7"`, false, nil},
/* 53 */ {`"1.5" == "7"`, false, nil}, /* 53 */ {`"1.5" == "7"`, false, nil},
/* 54 */ {`"1.5" != "7"`, true, nil}, /* 54 */ {`"1.5" != "7"`, true, nil},
/* 55 */ {"1.5 < ", nil, errors.New(`[1:6] 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, errors.New(`[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, errors.New(`[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, errors.New(`[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, errors.New(`[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, errors.New(`[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, errors.New(`[1:8] 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, errors.New(`[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, errors.New(`[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, errors.New(`[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}, /* 65 */ {"+1.5", float64(1.5), nil},
/* 66 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one not nil operand`)}, /* 66 */ {"+", nil, `[1:2] prefix operator "+" requires one not nil operand`},
/* 67 */ {"4 / 0", nil, errors.New(`division by zero`)}, /* 67 */ {"4 / 0", nil, `division by zero`},
/* 68 */ {"4.0 / 0", nil, errors.New(`division by zero`)}, /* 68 */ {"4.0 / 0", nil, `division by zero`},
/* 69 */ {"4.0 / \n2", float64(2.0), nil}, /* 69 */ {"4.0 / \n2", float64(2.0), nil},
/* 70 */ {`123`, int64(123), nil}, /* 70 */ {`123`, int64(123), nil},
/* 71 */ {`1.`, float64(1.0), nil}, /* 71 */ {`1.`, float64(1.0), nil},
@@ -100,7 +91,7 @@ func TestGeneralParser(t *testing.T) {
/* 77 */ {`5 % 2`, int64(1), nil}, /* 77 */ {`5 % 2`, int64(1), nil},
/* 78 */ {`5 % (-2)`, int64(1), nil}, /* 78 */ {`5 % (-2)`, int64(1), nil},
/* 79 */ {`-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}, /* 81 */ {`"a" < "b" AND NOT (2 < 1)`, true, nil},
/* 82 */ {`"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}, /* 83 */ {`"a" < "b" AND ~ 2 == 1`, true, nil},
@@ -113,12 +104,12 @@ func TestGeneralParser(t *testing.T) {
/* 90 */ {`x=2 but x*10`, int64(20), nil}, /* 90 */ {`x=2 but x*10`, int64(20), nil},
/* 91 */ {`false and true`, false, nil}, /* 91 */ {`false and true`, false, nil},
/* 92 */ {`false and (x==2)`, 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}, /* 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}, /* 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`)}, /* 97 */ {`2=5`, nil, `[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`)}, /* 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}, /* 99 */ {`2+(a=5)`, int64(7), nil},
/* 100 */ {`x ?? "default"`, "default", nil}, /* 100 */ {`x ?? "default"`, "default", nil},
/* 101 */ {`x="hello"; x ?? "default"`, "hello", nil}, /* 101 */ {`x="hello"; x ?? "default"`, "hello", nil},
@@ -128,98 +119,31 @@ func TestGeneralParser(t *testing.T) {
/* 105 */ {`1 ? {"a"} : {"b"}`, "b", nil}, /* 105 */ {`1 ? {"a"} : {"b"}`, "b", nil},
/* 106 */ {`10 ? {"a"} : {"b"} :: {"c"}`, "c", nil}, /* 106 */ {`10 ? {"a"} : {"b"} :: {"c"}`, "c", nil},
/* 107 */ {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", 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}, /* 109 */ {`10 ? {"a"} :[10] {x="b" but x} :: {"c"}`, "b", nil},
/* 110 */ {`10 ? {"a"} :[10] {x="b"; 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`)}, /* 111 */ {`10 ? {"a"} : {"b"}`, nil, `[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`)}, /* 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}, /* 113 */ {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
/* 114 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil}, /* 114 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
/* 115 */ {`nil`, nil, nil}, /* 115 */ {`nil`, nil, nil},
/* 116 */ {`null`, nil, errors.New(`undefined variable or function "null"`)}, /* 116 */ {`null`, nil, `undefined variable or function "null"`},
/* 117 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)}, /* 117 */ {`{"key"}`, nil, "[1:8] expected `:`, got `}`"},
/* 118 */ {`{"key":}`, nil, errors.New(`[1:9] expected "dictionary-value", got "}"`)}, /* 118 */ {`{"key":}`, nil, "[1:9] expected `dictionary-value`, got `}`"},
/* 119 */ {`{}`, &DictType{}, nil}, /* 119 */ {`{}`, &DictType{}, nil},
/* 120 */ {`v=10; v++; v`, int64(11), nil}, /* 120 */ {`v=10; v++; v`, int64(11), nil},
/* 121 */ {`1+1|2+0.5`, float64(2), nil}, /* 121 */ {`1+1|2+0.5`, float64(2), nil},
/* 122 */ {`1.2()`, newFraction(6, 5), nil}, /* 122 */ {`1.2()`, newFraction(6, 5), nil},
/* 123 */ {`1|(2-2)`, nil, errors.New(`division by zero`)}, /* 123 */ {`1|(2-2)`, nil, `division by zero`},
/* 124 */ {`x="abc"; x ?! #x`, int64(3), nil},
/* 125 */ {`x ?! #x`, nil, `[1:7] prefix/postfix operator "#" do not support operand '<nil>' [nil]`},
/* 126 */ {`x ?! (x+1)`, nil, nil},
/* 127 */ {`"abx" ?! (x+1)`, nil, `[1:6] left operand of "?!" must be a variable`},
/* 128 */ {`"abx" ?? "pqr"`, nil, `[1:6] left operand of "??" must be a variable`},
/* 129 */ {`"abx" ?= "pqr"`, nil, `[1:6] left operand of "?=" must be a variable`},
} }
// t.Setenv("EXPR_PATH", ".") // t.Setenv("EXPR_PATH", ".")
// parserTestSpec(t, section, inputs, 102) //runTestSuiteSpec(t, section, inputs, 130)
parserTest(t, section, inputs) runTestSuite(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(ctx)
logTest(t, count, section, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good = true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
eq := reflect.DeepEqual(gotResult, input.wantResult)
if !eq /*gotResult != input.wantResult*/ {
t.Errorf("%d: %q -> result = %v [%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)
}
} }

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