Compare commits

...

136 Commits

Author SHA1 Message Date
camoroso 723976b37e merged with datasource-context 2024-04-27 06:19:12 +02:00
camoroso 361b84f31f moved the exportVar() and exportFunc() functions from data-cursor.go to context-helpers.go 2024-04-27 06:16:11 +02:00
camoroso 70892aa980 removed commented code 2024-04-27 06:14:09 +02:00
camoroso 10eec286fa new operator '111123' that returns the content of the current context or the context of an iterator 2024-04-27 06:12:30 +02:00
camoroso 894b1884eb temporary 2024-04-26 21:03:22 +02:00
camoroso d2bab5fd9e context clone & export function moved from operand-func.go to context-helpers.go 2024-04-26 21:03:00 +02:00
camoroso f94f369547 context clone & export function moved from operand-func.go to context-helpers.go 2024-04-26 20:12:56 +02:00
camoroso 107ec4958f operator-length.go: the prefix operator '#' now accept iterator oeprand; it returns the index of the current item 2024-04-26 08:04:55 +02:00
camoroso a22047e84e operand-func.go: removed commented code 2024-04-26 08:02:52 +02:00
camoroso 80b7d5b988 operand-dict.go: removed commented code 2024-04-26 08:02:22 +02:00
camoroso 2ab896bbac the dataCursor struct has been moved fro operand-iterator.go to the new file data-cursor.go 2024-04-26 08:01:35 +02:00
camoroso 62e16219f7 func-math.go/add: now supports the general iterator interface 2024-04-26 04:47:59 +02:00
camoroso 750c660331 first working implementation of iterators and some fixes to the parser around lists analysis 2024-04-26 04:45:42 +02:00
camoroso 7a88449cd1 updated some error messages; add some tests on the dict data-type; Use of reflect.DeepEqual() to compare the test results with the desired results. 2024-04-26 04:43:36 +02:00
camoroso b14dc2f1ee provisional implementation of the postfix ++ operator 2024-04-26 04:37:50 +02:00
camoroso d354102c6a adapted to the new GetFuncInfo() specification 2024-04-26 04:36:03 +02:00
camoroso 761ec868e6 operator-assign.go: little refactor 2024-04-26 04:31:31 +02:00
camoroso 7941c2dfec the FlatArrayIterator has been moved to the new file iter-list.go 2024-04-26 04:30:43 +02:00
camoroso ebb2811ed3 context.go: added exists return value to the GetFuncInfo() 2024-04-26 04:28:50 +02:00
camoroso 75c0c0f681 Fix ast.go: the insert() didn't check the returned error 2024-04-26 04:26:20 +02:00
camoroso 268a968548 term.go: added two new priorities (priIterValue and priPrePost); new function term.symbol()) 2024-04-26 04:23:39 +02:00
camoroso 323308d86f expressions now support dict data-type 2024-04-21 14:24:56 +02:00
camoroso b28d6a8f02 commented out a test for a future new operator based on the caret symbol 2024-04-21 07:12:59 +02:00
camoroso ab82bcf1ef preparation for the definition of the iterators 2024-04-21 07:11:58 +02:00
camoroso a628bfac39 New symbol '^' (caret). The scanner now returns an error token if can't recognise a symbol. 2024-04-21 07:10:19 +02:00
camoroso d1122da566 coalesce operators '??' and '?=' now accepts function definitions too 2024-04-20 09:40:07 +02:00
camoroso 6ae5ca34ed expr_test.go, more tests on the int() function 2024-04-20 08:50:05 +02:00
camoroso 730b59e6d3 funcs_test.go, more tests on the int() function 2024-04-20 07:41:58 +02:00
camoroso f198ba47e1 new convert function int() 2024-04-20 07:29:42 +02:00
camoroso 943ef3327e empty expressions no more return error, now they return nil 2024-04-20 06:56:26 +02:00
camoroso 475ef3c80a parser_test.go: test added on isNil() function 2024-04-20 06:54:51 +02:00
camoroso 3c0307524b simple-func-store.go: now imports 'builtins' module on creation 2024-04-20 06:53:30 +02:00
camoroso c27e487fc3 new function isNil() 2024-04-20 06:52:33 +02:00
camoroso ed973c9b7b fixed all errors in test files 2024-04-20 06:04:35 +02:00
camoroso 15bbfacd47 all constant value are now stored in the same data struct (same constructor). Also nil const added 2024-04-20 05:39:49 +02:00
camoroso 04f934ab04 'nil' keyword added 2024-04-20 05:38:00 +02:00
camoroso 591b4ffc19 Expr.adoc: 'work in progress' added 2024-04-19 14:30:40 +02:00
camoroso fe9ab9ebd2 Expr.adoc, corrected a typo 2024-04-19 09:20:01 +02:00
camoroso 7198749063 new operator dot '.' used to select an item or character from a list or a string respectively 2024-04-19 09:05:26 +02:00
camoroso b76481bbf2 new operator 'builtin' 2024-04-19 00:19:11 +02:00
camoroso 4f05e5c90a funcs-math.go registered as 'math.arith' 2024-04-19 00:18:23 +02:00
camoroso 8ad25afdc4 function register 2024-04-19 00:16:49 +02:00
camoroso 54bc759f70 expressions now support intger value in bin (0b), oct (0o), and hex (0x) form 2024-04-17 14:15:50 +02:00
camoroso b6887af77a added the prefix operator '#' (length of string and array) 2024-04-17 07:12:32 +02:00
camoroso 353d495c50 two new operators added: '<<' and '>> 2024-04-16 03:54:50 +02:00
camoroso f45b2c0a88 func-os.go: set of functions to work with files 2024-04-15 07:01:34 +02:00
camoroso 624e3ac0f2 parser_test.go: changed ImportImportFunc(s) 2024-04-15 07:00:40 +02:00
camoroso 35fcbd2bce expr_test.go: added a test in passing functions as parameters 2024-04-15 06:59:27 +02:00
camoroso 2150181303 func-import.go: renamed ImportImportFunc as ImportImportFuncs 2024-04-15 06:57:29 +02:00
camoroso d643e24a1b doc: Syntax of expressions moved from README.adoc to doc/Expr.doc 2024-04-14 08:16:01 +02:00
camoroso 43e631f2e8 enabled passing functions as parameters in function call 2024-04-14 07:38:28 +02:00
camoroso f1afbf9b49 parser.go: removed commented code 2024-04-14 07:35:43 +02:00
camoroso ed6af6603a commented out some test code 2024-04-13 22:15:20 +02:00
camoroso 51e740d243 expr_test.go: fixed test expression 2024-04-13 22:14:28 +02:00
camoroso 53dacd5332 operator-selector.go: fixed the case match algorithm 2024-04-13 22:13:16 +02:00
camoroso e297f2a1d3 expr_test.go: new test file to verify complex expressions 2024-04-13 10:11:44 +02:00
camoroso 70cdb9367e changed the structer of the selector components: now all case are a list value assigned as right operand of the selector operator 2024-04-13 10:10:25 +02:00
camoroso efd9af9030 graph.go: remove unused Reticle code 2024-04-13 06:05:04 +02:00
camoroso b67d896415 Merge branch 'main' into feat_graph 2024-04-13 06:01:06 +02:00
camoroso f03ae555fc added the copywrite comment header in source files that lacked it 2024-04-13 06:00:22 +02:00
camoroso f36ae33acc added the graph source files 2024-04-13 05:54:34 +02:00
camoroso b9ea96f649 merge: solved a lot of conflicts caused by an incorect commit removal 2024-04-13 05:47:10 +02:00
camoroso 838ab9fd7e control: exported all control variables by renaming them in upper case 2024-04-13 05:16:23 +02:00
camoroso ad6b4b872f token_test.go: rewritten using a more structured form 2024-04-13 05:08:38 +02:00
camoroso c75e6485e0 term_test.go: changed some fmt.Println() to t.Log() 2024-04-13 05:07:38 +02:00
camoroso 7b80f7f03b helpers_test.go: removed the printed messages "Hello World!" 2024-04-13 05:06:43 +02:00
camoroso 7f9fd570b2 parser_test.go: changed the notice message about EXPR_PATH 2024-04-13 04:40:48 +02:00
camoroso b17d2250a4 parser.go: fixed a problem on the selector operator 2024-04-13 04:37:15 +02:00
camoroso dda10749d0 token.go: better implementation of the function String() 2024-04-13 04:33:59 +02:00
camoroso 07ca84170e operand-selector-case.go: String() function added to the selectorCase type 2024-04-13 04:32:54 +02:00
camoroso 55e136e9bc ast.go: String() function addedd to the Expr interface 2024-04-13 04:31:53 +02:00
camoroso e493c40c7b builtin-funcs: export import functions (made their names uppercase) 2024-04-13 04:18:14 +02:00
camoroso 95605232ab README.adoc: updated draft 2024-04-09 09:11:47 +02:00
camoroso 8f396a35de ExprContext.SetVar() no longer requires the explicit specification of the type of number 2024-04-09 07:12:22 +02:00
camoroso bd323efedf README.adoc: example programs updated 2024-04-09 07:09:23 +02:00
camoroso a9b143d012 README.adoc: example programs updated 2024-04-09 06:29:44 +02:00
camoroso 024ff42be0 Arg struct members are now exported 2024-04-09 06:28:57 +02:00
camoroso 8ab2c28343 EvalArg -> Arg 2024-04-09 06:13:12 +02:00
camoroso c4a2fcce3d README.adoc: proofreading 2024-04-09 05:42:50 +02:00
camoroso 4d94a7ad59 class and kind types removed 2024-04-09 05:32:50 +02:00
camoroso 9ac88bf446 made some interfaces exportable and fixed/enhaaced some selector operator versions 2024-04-08 23:17:56 +02:00
camoroso aa195b9a68 README.adoc: section on selector operator 2024-04-08 23:14:25 +02:00
camoroso d3f388f7e1 Selector operator, multi-operand, added 2024-04-08 22:16:07 +02:00
camoroso f74e523617 test on '@@' 2024-04-06 03:09:02 +02:00
camoroso 7612a59757 Operator '@@' (export-all) added. Experimental include() function also added 2024-04-06 03:06:07 +02:00
camoroso ce6b88ccdd present.go -> control.go 2024-04-06 03:01:57 +02:00
camoroso 43b7d3b15e constant prefix name 'preset_' changed to 'control_'. 'control_export_all' constant added. 2024-04-06 03:01:17 +02:00
camoroso 181a9210d5 parser.go -- Fix: reset to true the firstToken flag after getting a semicolon 2024-04-06 01:32:29 +02:00
camoroso 7f9d7690f9 SimpleFuncStore is now derived from SimpleVarStore 2024-04-06 01:07:06 +02:00
camoroso 0ba96e65a5 Variable reference 2024-04-06 01:00:29 +02:00
camoroso 574a6f5215 Expression process diagram added 2024-04-04 12:56:43 +02:00
camoroso d073d11ad3 Variable references belonging to the parent scope added ('@') 2024-04-04 12:54:26 +02:00
camoroso 8c3254a8f2 typo 2024-04-03 13:19:18 +02:00
camoroso fccfd2f971 Operators '??' and '?=' added 2024-04-03 13:15:25 +02:00
camoroso 088e347c95 Changed SetValue() as SetVar() and GetValue() as GetVar() 2024-04-03 06:29:57 +02:00
camoroso 6c02b02d4a New draft 2024-04-03 06:23:45 +02:00
camoroso 4fc8ac64e7 README.adoc: examples, relational operators table completed 2024-04-02 08:18:34 +02:00
camoroso 4683a08da2 import() function added 2024-04-02 06:49:16 +02:00
camoroso 6aada9f7e4 funcs-math.go -> func-math.go 2024-04-02 05:18:30 +02:00
camoroso 4e8ebbef04 the preset.go file 2024-04-02 05:02:15 +02:00
camoroso f29b1f13b1 preset definitions and functions were moved to the new preset.go file 2024-04-02 05:00:48 +02:00
camoroso 072dab4144 Expressions now support function definition 2024-04-02 04:36:03 +02:00
camoroso f58ec3ac32 parser: now checks whether the assign operator '=' is preceded by a variable 2024-03-31 06:38:46 +02:00
camoroso 28e3b2f741 parser: accepts espression forest (multi expressions) 2024-03-31 06:10:27 +02:00
camoroso 4e361f938e ast: now supports espression forest (multi expressions) 2024-03-31 05:57:02 +02:00
camoroso aa705e68bf 'and' and 'or' operators are now evaluated using logic shortcut (this behaviour can be changed setting system var '_bool_shortcut' to false 2024-03-31 05:09:24 +02:00
camoroso 94ad968d5e ast.go: presetting of system variables before starting evaluation process 2024-03-31 05:06:24 +02:00
camoroso e3e5ad7da8 Farmatting changes 2024-03-30 08:12:47 +01:00
camoroso d0572260c7 New operators 'but' and assignment ('=') 2024-03-30 08:09:41 +01:00
camoroso 43fd457383 term.go: added priority levels priBut and priAssign 2024-03-30 08:08:49 +01:00
camoroso e085502df3 symbol.go: New keyword 'but' 2024-03-30 08:08:11 +01:00
camoroso 85fb007a2b The plus and minus operators now support lists for join and difference respectively 2024-03-30 07:05:22 +01:00
camoroso f540ec28e8 funcs-math.go: Implementes add() and mul() function for both simple values and iterators 2024-03-30 07:01:00 +01:00
camoroso 36feed3168 parser.go: changedthe message error about unknown functiona 2024-03-30 06:58:29 +01:00
camoroso 836a9165a5 Added Iterator interface and two implementation: list itrator and range iterator 2024-03-30 06:56:12 +01:00
camoroso e012afa691 Added the RegisterFunc() interface to the expression context 2024-03-30 06:54:43 +01:00
camoroso 20007a5a81 utils.go: New function isList() that checks is a value is an array of any 2024-03-30 06:50:49 +01:00
camoroso 107484d13c parser_test.go: add test on the list data type 2024-03-28 08:52:54 +01:00
camoroso c36c88d0fd Added list '[]' data type. Fix: function with no arguments 2024-03-28 08:51:02 +01:00
camoroso 3ca415fc66 Merge branch 'main' of ssh://git.portale-stac.it:3022/go-pkg/expr 2024-03-28 06:34:53 +01:00
camoroso ae2f7c89bd helpers_test.go: changed some comments 2024-03-28 06:34:40 +01:00
camoroso 27f53db0f4 helpers_test.go: tests on the helper functions 2024-03-28 06:33:14 +01:00
camoroso 6d8c6f5154 helpers.go provides easy functions to parse and evaluate expressions 2024-03-28 06:32:37 +01:00
camoroso d20027296c New test on the tilde operator. Now it is based on simple-var-store 2024-03-28 06:30:45 +01:00
camoroso 37f0de5902 Added simple context objects 2024-03-28 06:29:11 +01:00
camoroso fdc2fd8dfd utils.go: added functions anyInteger() and anyFloat() 2024-03-28 06:26:20 +01:00
camoroso 9fa3d9fcb2 Added the tilde '~' operator acting as NOT 2024-03-28 06:25:29 +01:00
camoroso 45c0663ea1 Second incomplete draft 2024-03-27 08:44:47 +01:00
camoroso 998580772a added operator '%' (division remainder) and test 2024-03-26 09:27:14 +01:00
camoroso 594806c999 added operator './' (float division) and test 2024-03-26 09:12:44 +01:00
camoroso 800664c98c added go.mod 2024-03-26 08:56:20 +01:00
camoroso 4bb1e9abcd Merge branch 'main' of ssh://git.portale-stac.it:3022/go-pkg/expr 2024-03-26 08:45:37 +01:00
camoroso 33841c5861 Added copyrighr note to all sources 2024-03-26 08:45:18 +01:00
camoroso eaf5e29a0c byte-ring.go -> byte-slider.go 2024-03-26 08:44:56 +01:00
camoroso 3586aadbc1 First incomplete draft 2024-03-26 07:36:12 +01:00
camoroso 19ab171109 README.md -> README.adoc 2024-03-26 07:17:27 +01:00
70 changed files with 4538 additions and 607 deletions
+147
View File
@@ -0,0 +1,147 @@
= README
README about the Expressions calculator
:authors: Celestino Amoroso
:docinfo: shared
:encoding: utf-8
:toc: right
:toclevels: 4
//:toc-title: Indice Generale
:icons: font
:icon-set: fi
:numbered:
//:table-caption: Tabella
//:figure-caption: Diagramma
:docinfo1:
:sectlinks:
:sectanchors:
:source-highlighter: rouge
// :rouge-style: ThankfulEyes
:rouge-style: gruvbox
// :rouge-style: colorful
//:rouge-style: monokay
toc::[]
== Expr
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
A few examples to get started.
.Examples taken from parser_test.go
[source,go]
----
`1.0 / 2` // 0.5
`435 + 105 * 2 - 1` // 644
`4 == (3-1)*(10/5)` // true
`"uno" * (2+1)` // `unounouno`
`2+3 but 5*2` // 10 <1>
`add(add(1+4),3+2,5*(3-2))` // 15 <2>
`a=5; b=2; add(a, b*3)` // 11 <3>
`two=func(){2}; two()` // 2 <4>
`double=func(x){2*x}; a=4+1; two=func() {2}; (double(3+a) + 1) * two()` // 34
`import("./test-funcs.expr"); (double(3+a) + 1) * two()` // 34 <5>
`[1,2,"hello"]` // Mixed types list
`[1,2]+[3]` // append list, result: [1,2,3]
`add([1,[2,2],3,2])` // Deep list sum, result: 10 <2>
`[a=1,b=2,c=3] but a+b+c` // 6
----
<1> [blue]`but` operator.
<2> The _add()_ function definition may be changed in the future.
<3> Multiple expression. Only the last expression value will returned.
<4> Simple function definition: _two()_ returns 2.
<5> _import()_ function imports expressions from the specified files. See file _test-funcs.expr_.
=== Usage
[source,go]
----
package main
import (
"fmt"
"strings"
"git.portale-stac.it/go-pkg/expr"
)
func main() {
ctx := expr.NewSimpleVarStore()
ctx.SetVar("var", 4)
source := `(3-1)*(10/5) == var`
r := strings.NewReader(source)
scanner := expr.NewScanner(r, expr.DefaultTranslations())
parser := expr.NewParser(ctx)
if ast, err := parser.Parse(scanner); err == nil {
if result, err := ast.Eval(ctx); err == nil {
fmt.Printf("%q -> %v [%T]\n", source, result, result)
} else {
fmt.Println("Error calculating the expression:", err)
}
} else {
fmt.Println("Error parsing the expression:", err)
}
}
----
The above program is equivalent to the following one.
[source,go]
----
package main
import (
"fmt"
"git.portale-stac.it/go-pkg/expr"
)
func main() {
ctx := expr.NewSimpleVarStore()
ctx.SetVar("var", 4)
source := `(3-1)*(10/5) == var`
if result, err := expr.EvalString(ctx, source); err == nil {
fmt.Printf("%q -> %v [%T]\n", source, result, result)
} else {
fmt.Println("Error calculating the expression:", err)
}
}
----
Here is another equivalent version.
[source,go]
----
package main
import (
"fmt"
"git.portale-stac.it/go-pkg/expr"
)
func main() {
source := `(3-1)*(10/5) == var`
if result, err := expr.EvalStringA(source, expr.Arg{"var", 4}); err == nil {
fmt.Printf("%q -> %v [%T]\n", source, result, result)
} else {
fmt.Println("Error calculating the expression:", err)
}
}
----
== Context of evaluation
Unless helpers functions like _expr.EvalStringA()_ are used, a _context_ is required to compute an expession.
A context is an object that implements the _expr.ExprContext_ interface. This interface specifies a set of function to handle variables and functions.
Variables and functions can be added to a context both programmatically and ad an effect of the expression computation.
== Expressions syntax
See #TODO link to doc/Expr.html#
View File
+64 -12
View File
@@ -1,22 +1,40 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// ast.go
package expr
import (
"errors"
"fmt"
"strings"
)
type Expr interface {
Eval(ctx ExprContext) (result any, err error)
eval(ctx ExprContext, preset bool) (result any, err error)
String() string
}
//-------- ast
type ast struct {
root *term
forest []*term
root *term
}
func NewAst() *ast {
return &ast{}
}
func (self *ast) ToForest() {
if self.root != nil {
if self.forest == nil {
self.forest = make([]*term, 0)
}
self.forest = append(self.forest, self.root)
self.root = nil
}
}
func (self *ast) String() string {
var sb strings.Builder
if self.root == nil {
@@ -37,10 +55,15 @@ func (self *ast) addTokens(tokens ...*Token) (err error) {
}
func (self *ast) addToken(tk *Token) (err error) {
if t := newTerm(tk, nil); t != nil {
_, err = self.addToken2(tk)
return
}
func (self *ast) addToken2(tk *Token) (t *term, err error) {
if t = newTerm(tk, nil); t != nil {
err = self.addTerm(t)
} else {
err = fmt.Errorf("No term constructor for token %q", tk.String())
err = tk.Errorf("No term constructor for token %q", tk.String())
}
return
}
@@ -60,8 +83,9 @@ func (self *ast) insert(tree, node *term) (root *term, err error) {
if tree.isComplete() {
var subRoot *term
last := tree.removeLastChild()
subRoot, err = self.insert(last, node)
subRoot.setParent(tree)
if subRoot, err = self.insert(last, node); err == nil {
subRoot.setParent(tree)
}
} else {
node.setParent(tree)
}
@@ -69,16 +93,44 @@ func (self *ast) insert(tree, node *term) (root *term, err error) {
root = node
tree.setParent(node)
} else {
err = fmt.Errorf("two adjacent operators: %q and %q", tree, node)
err = node.Errorf("two adjacent operators: %q and %q", tree, node)
}
return
}
func (self *ast) eval(ctx exprContext) (result any, err error) {
func (self *ast) Finish() {
if self.root == nil && self.forest != nil && len(self.forest) >= 1 {
self.root = self.forest[len(self.forest)-1]
self.forest = self.forest[0 : len(self.forest)-1]
}
}
func (self *ast) Eval(ctx ExprContext) (result any, err error) {
return self.eval(ctx, true)
}
func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
self.Finish()
if self.root != nil {
result, err = self.root.compute(ctx)
} else {
err = errors.New("empty expression")
if preset {
initDefaultVars(ctx)
}
if self.forest != nil {
for _, root := range self.forest {
if result, err = root.compute(ctx); err == nil {
ctx.setVar(ControlLastResult, result)
} else {
//err = fmt.Errorf("error in expression nr %d: %v", i+1, err)
break
}
}
}
if err == nil {
result, err = self.root.compute(ctx)
}
// } else {
// err = errors.New("empty expression")
}
return
}
+12 -9
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// ast_test.go
package expr
@@ -14,9 +17,9 @@ func TestAstString(t *testing.T) {
}
func TestAddTokensGood(t *testing.T) {
tk1 := NewValueToken(SymInteger, "100", 100)
tk2 := NewToken(SymPlus, "+")
tk3 := NewValueToken(SymInteger, "50", 500)
tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
tk2 := NewToken(0, 0, SymPlus, "+")
tk3 := NewValueToken(0, 0, SymInteger, "50", 500)
tree := NewAst()
if gotErr := tree.addTokens(tk1, tk2, tk3); gotErr != nil {
@@ -25,12 +28,12 @@ func TestAddTokensGood(t *testing.T) {
}
func TestAddTokensBad(t *testing.T) {
tk0 := NewValueToken(SymInteger, "200", 200)
tk1 := NewValueToken(SymInteger, "100", 100)
tk2 := NewToken(SymPlus, "+")
tk3 := NewValueToken(SymInteger, "50", 500)
tk0 := NewValueToken(0, 0, SymInteger, "200", 200)
tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
tk2 := NewToken(0, 0, SymPlus, "+")
tk3 := NewValueToken(0, 0, SymInteger, "50", 500)
wantErr := errors.New(`two adjacent operators: "200" and "100"`)
wantErr := errors.New(`[0:0] two adjacent operators: "200" and "100"`)
tree := NewAst()
if gotErr := tree.addTokens(tk0, tk1, tk2, tk3); gotErr != nil && gotErr.Error() != wantErr.Error() {
@@ -39,7 +42,7 @@ func TestAddTokensBad(t *testing.T) {
}
func TestAddUnknownTokens(t *testing.T) {
tk0 := NewToken(SymPercent, "%")
tk0 := NewToken(0, 0, SymPercent, "%")
wantErr := errors.New(`No term constructor for token "%"`)
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// byte-slider.go
package expr
+41
View File
@@ -0,0 +1,41 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// context-helpers.go
package expr
func cloneContext(sourceCtx ExprContext) (clonedCtx ExprContext) {
if sourceCtx != nil {
clonedCtx = sourceCtx.Clone()
}
return
}
func exportVar(ctx ExprContext, name string, value any) {
if name[0] == '@' {
name = name[1:]
}
ctx.setVar(name, value)
}
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
if name[0] == '@' {
name = name[1:]
}
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
}
func exportObjects(destCtx, sourceCtx ExprContext) {
exportAll := isEnabled(sourceCtx, control_export_all)
// Export variables
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
refValue, _ := sourceCtx.GetVar(refName)
exportVar(destCtx, refName, refValue)
}
// Export functions
for _, refName := range sourceCtx.EnumFuncs(func(name string) bool { return exportAll || name[0] == '@' }) {
if info, _ := sourceCtx.GetFuncInfo(refName); info != nil {
exportFunc(destCtx, refName, info)
}
}
}
+32 -5
View File
@@ -1,15 +1,42 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// context.go
package expr
type exprFunc interface {
// ---- Function template
type FuncTemplate func(ctx ExprContext, name string, args []any) (result any, err error)
// ---- Functor interface
type Functor interface {
Invoke(ctx ExprContext, name string, args []any) (result any, err error)
}
type simpleFunctor struct {
f FuncTemplate
}
func (functor *simpleFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
return functor.f(ctx, name, args)
}
// ---- Function Info
type ExprFunc interface {
Name() string
MinArgs() int
MaxArgs() int
Functor() Functor
}
type exprContext interface {
GetValue(varName string) (value any, exists bool)
SetValue(varName string, value any)
GetFuncInfo(name string) exprFunc
// ----Expression Context
type ExprContext interface {
Clone() ExprContext
GetVar(varName string) (value any, exists bool)
SetVar(varName string, value any)
setVar(varName string, value any)
EnumVars(func(name string) (accept bool)) (varNames []string)
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
GetFuncInfo(name string) (item ExprFunc, exists bool)
Call(name string, args []any) (result any, err error)
RegisterFunc(name string, f Functor, minArgs, maxArgs int)
}
+62
View File
@@ -0,0 +1,62 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// control.go
package expr
import "strings"
// Preset control variables
const (
ControlLastResult = "last"
ControlBoolShortcut = "_bool_shortcut"
ControlImportPath = "_import_path"
)
// Other control variables
const (
control_export_all = "_export_all"
)
// Initial values
const (
init_import_path = "~/.local/lib/go-pkg/expr:/usr/local/lib/go-pkg/expr:/usr/lib/go-pkg/expr"
)
func initDefaultVars(ctx ExprContext) {
ctx.SetVar(ControlBoolShortcut, true)
ctx.SetVar(ControlImportPath, init_import_path)
}
func enable(ctx ExprContext, name string) {
if strings.HasPrefix(name, "_") {
ctx.SetVar(name, true)
} else {
ctx.SetVar("_"+name, true)
}
}
func disable(ctx ExprContext, name string) {
if strings.HasPrefix(name, "_") {
ctx.SetVar(name, false)
} else {
ctx.SetVar("_"+name, false)
}
}
func isEnabled(ctx ExprContext, name string) (status bool) {
if v, exists := ctx.GetVar(name); exists {
if b, ok := v.(bool); ok {
status = b
}
}
return
}
func getControlString(ctx ExprContext, name string) (s string, exists bool) {
var v any
if v, exists = ctx.GetVar(name); exists {
s, exists = v.(string)
}
return
}
+62
View File
@@ -0,0 +1,62 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// data-cursors.go
package expr
import (
"io"
)
const (
initName = "init"
nextName = "next"
currentName = "current"
)
type dataCursor struct {
ds map[any]*term
ctx ExprContext
index int
resource any
nextFunc Functor
currentFunc Functor
}
func newDataCursor(ctx ExprContext) (dc *dataCursor) {
dc = &dataCursor{
index: -1,
ctx: ctx.Clone(),
}
return
}
func (dc *dataCursor) String() string {
return "$(...)"
}
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
ctx := cloneContext(dc.ctx)
if item, err = dc.currentFunc.Invoke(ctx, currentName, []any{}); err == nil && item == nil {
err = io.EOF
}
exportObjects(dc.ctx, ctx)
return
}
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item
ctx := cloneContext(dc.ctx)
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{}); err == nil {
if item == nil {
err = io.EOF
} else {
dc.index++
}
}
exportObjects(dc.ctx, ctx)
return
}
func (dc *dataCursor) Index() int {
return dc.index
}
+278
View File
@@ -0,0 +1,278 @@
= Expr
Expressions calculator
:authors: Celestino Amoroso
:docinfo: shared
:encoding: utf-8
:toc: right
:toclevels: 4
//:toc-title: Indice Generale
:icons: font
:icon-set: fi
:numbered:
//:table-caption: Tabella
//:figure-caption: Diagramma
:docinfo1:
:sectlinks:
:sectanchors:
:source-highlighter: rouge
// :rouge-style: ThankfulEyes
:rouge-style: gruvbox
// :rouge-style: colorful
//:rouge-style: monokay
toc::[]
#TODO: Work in progress#
== Expr
_Expr_ is a GO package capable of analysing, interpreting and calculating expressions.
=== Concepts and terminology
#TODO#
== Data types
_Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists.
=== Numbers
Numbers can be integers (GO int64) or float (GO float64). In mixed operations involving integers and floats, integers are automatically promoted to floats.
.Arithmetic operators
[cols="^1,^2,6,4"]
|===
| Symbol | Operation | Description | Examples
| [blue]`+` / [blue]`-` | _change sign_ | Change the sign of values | [blue]`-1` _[-1]_ +
[blue]`-(+2)` _[-2]_
| [blue]`+` | _sum_ | Add two values | [blue]`-1 + 2` _[1]_ +
[blue]`4 + 0.5` _[4.5]_
| [blue]`-` | _subtraction_ | Subtract the right value from the left one | [blue]`3 - 1` _[2]_ +
[blue]`4 - 0.5` _[3.5]_
| [blue]`*` | _product_ | Multiply two values | `-1 * 2` _[-2]_ +
[blue]`4 * 0.5` _[2.0]_
| [blue]`/` | _Division_ | Divide the left value by the right one | [blue]`-1 / 2` _[0]_ +
[blue]`1.0 / 2` _[0.5]_
| [blue]`./` | _Float division_ | Force float division | [blue]`-1 ./ 2` _[-0.5]_
| [blue]`%` | _Modulo_ | Remainder of the integer division | [blue]`5 % 2` _[1]_
|===
=== String
Strings are character sequences enclosed between two double quote [blue]`"`. Example: [blue]`"I'm a string"`.
Some arithmetic operators can also be used with strings.
.String operators
[cols="^1,^2,6,4"]
|===
| Symbol | Operation | Description | Examples
| [blue]`+` | _concatenation_ | Join two strings or two _stringable_ values | [blue]`"one" + "two"` _["onetwo"]_ +
[blue]`"one" + 2` _["one2"]_
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` _["oneone"]_
|===
=== Boolean
Boolean data type has two values only: _true_ and _false_. Relational and Boolean expressions produce Boolean values.
.Relational operators
[cols="^1,^2,6,4"]
|===
| Symbol | Operation | Description | Examples
| [blue]`==` | _Equal_ | True if the left value is equal to the right one | [blue]`5 == 2` _[false]_ +
[blue]`"a" == "a"` _[true]_
| [blue]`!=` | _Not Equal_ | True if the left value is NOT equal to the right one | [blue]`5 != 2` _[true]_ +
[blue]`"a" != "a"` _[false]_
| [blue]`<` | _Less_ | True if the left value is less than the right one | [blue]`5 < 2` _[false]_ +
[blue]`"a" < "b"` _[true]_
| [blue]`\<=` | _Less or Equal_ | True if the left value is less than or equal to the right one | [blue]`5 \<= 2` _[false]_ +
[blue]`"b" \<= "b"` _[true]_
| [blue]`>` | _Greater_ | True if the left value is greater than the right one | [blue]`5 > 2` _[true]_ +
[blue]`"a" < "b"` _[false]_
| [blue]`>=` | _Greater or Equal_ | True if the left value is greater than or equal to the right one | [blue]`5 >= 2` _[true]_ +
[blue]`"b" \<= "b"` _[true]_
|===
.Boolean operators
[cols="^2,^2,5,4"]
|===
| Symbol | Operation | Description | Examples
| [blue]`NOT` | _Not_ | True if the right value is false | [blue]`NOT true` _[false]_ +
[blue]`NOT (2 < 1)` _[true]_
| [blue]`AND` / [blue]`&&` | _And_ | True if both left and right values are true | [blue]`false && true` _[false]_ +
[blue]`"a" < "b" AND NOT (2 < 1)` _[true]_
| [blue]`OR` / [blue]`\|\|` | _Or_ | True if at least one of the left and right values integers true| [blue]`false or true` _[true]_ +
[blue]`"a" == "b" OR (2 == 1)` _[false]_
|===
[CAUTION]
====
Currently, boolean operations are evaluated using _short cut evaluation_. This means that, if the left expression of operators [blue]`and` and [blue]`or` is sufficient to establish the result of the whole operation, the right expression would not evaluated at all.
.Example
[source,go]
----
2 > (a=1) or (a=8) > 0; a // <1>
----
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and as a conseguence its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
====
=== List
_Expr_ supports list of mixed-type values, also specified by normal expressions.
.List examples
[source,go]
----
[1, 2, 3] // List of integers
["one", "two", "three"] // List of strings
["one", 2, false, 4.1] // List of mixed-types
["one"+1, 2.0*(9-2)] // List of expressions
[ [1,"one"], [2,"two"]] // List of lists
----
.List operators
[cols="^2,^2,5,4"]
|===
| Symbol | Operation | Description | Examples
| [blue]`+` | _Join_ | Joins two lists | [blue]`[1,2] + [3]` _[ [1,2,3] ]_
| [blue]`-` | _Difference_ | Left list without elements in the right list | [blue]`[1,2,3] - [2]` _[ [1,3] ]_
|===
== Variables
A variable is an identifier with an assigned value. Variables are stored in the object that implements the _ExprContext_ interface.
.Examples
[source,go]
----
a=1
x = 5.2 * (9-3)
x = 1; y = 2*x
----
== Other operations
=== [blue]`;` operator
The semicolon operator [blue]`;` is an infixed operator. It evaluates the left expression first and then the right expression. The latter is the final result.
IMPORTANT: Technically [blue]`;` is not treated as a real operator. It acts as a separator in lists of expressions.
TIP: [blue]`;` can be used to set some variables before the final calculation.
.Example
[source,go]
----
a=1; b=2; c=3; a+b+c // returns 6
----
=== [blue]`but` operator
[blue]`but` is an infixed operator. Its operands can be any type of expression. It evaluates the left expression first, then the right expression. The value of the right expression is the final result. Examples: [blue]`5 but 2` returns 2, [blue]`x=2*3 but x-1` returns 5.
[blue]`but` is very similar to [blue]`;`. The only difference is that [blue]`;` can't be used inside parenthesis [blue]`(` and [blue]`)`.
=== Assignment operator [blue]`=`
The assignment operator [blue]`=` is used to define variable in the evaluation context or to change their value (see _ExprContext_).
The value on the left side of [blue]`=` must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.
.Example
[source,go]
----
a=15+1 // returns 16
----
=== Selector operator [blue]`? : ::`
The _selector operator_ is very similar to the _switch/case/default_ statement available in many programming languages.
.Syntax
[source,bnf]
----
<selector-operator> ::= <expression> "?" <selector-case> { ":" <selector-case> } ["::" <case-value>]
<selector-case> ::= [<list>] <case-value>
<case-value> ::= "{" <multi-expr> "}"
<multi-expr> ::= <expr> {";" <expr>}
----
.Example
[source,go]
----
1 ? {"a"} : {"b"} // returns "b"
10 ? {"a"} : {"b"} :: {"c"} // returns "c"
10 ? {"a"} :[true, 2+8] {"b"} :: {"c"} // returns "b"
10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"} // error: "... case list in default clause"
10 ? {"a"} :[10] {x="b" but x} :: {"c"} // returns "b"
10 ? {"a"} :[10] {x="b"; x} :: {"c"} // returns "b"
10 ? {"a"} : {"b"} // error: "... no case catches the value (10) of the selection expression
----
== Priorities of operators
The table below shows all supported operators by decreasing priorities.
.Operators priorities
[cols="^2,^2,^2,^5,<5"]
|===
| Priority | Operators | Position | Operation | Operands and results
1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ "!" -> _integer_
1+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| ("+"\|"-") _number_ -> _number_
.5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ "*" _number_ -> _number_
| [blue]`*` | _Infix_ | _String-repeat_ | _string_ "*" _integer_ -> _string_
| [blue]`/` | _Infix_ | _Division_ | _number_ "/" _number_ -> _number_
| [blue]`./` | _Infix_ | _Float-division_ | __number__ "./" _number_ -> _float_
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ "%" _integer_ -> _integer_
.5+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ "+" _number_ -> _number_
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) "+" (_string_\|_number_) -> _string_
| [blue]`+` | _Infix_ | _List-join_ | _list_ "+" _list_ -> _list_
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ "-" _number_ -> _number_
| [blue]`-` | _Infix_ | _List-difference_ | _list_ "-" _list_ -> _list_
.6+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ "<" _comparable_ -> _boolean_
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ "\<=" _comparable_ -> _boolean_
| [blue]`>` | _Infix_ | _greater_ | _comparable_ ">" _comparable_ -> _boolean_
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ ">=" _comparable_ -> _boolean_
| [blue]`==` | _Infix_ | _equal_ | _comparable_ "==" _comparable_ -> _boolean_
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ "!=" _comparable_ -> _boolean_
.1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | "not" _boolean_ -> _boolean_
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ "and" _boolean_ -> _boolean_
| [blue]`&&` | _Infix_ | _and_ | _boolean_ "&&" _boolean_ -> _boolean_
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ "or" _boolean_ -> _boolean_
| [blue]`\|\|` | _Infix_ | _or_ | _boolean_ "\|\|" _boolean_ -> _boolean_
.1+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
|===
== Functions
Functions in _Expr_ are very similar to functions in many programming languages.
In _Expr_ functions compute values in a local context (scope) that do not make effects on the calling context. This is the normal behavior. Using the reference operator [blue]`@` it is possibile to export local definition to the calling context.
=== Function calls
#TODO: function calls operations#
=== Function definitions
#TODO: function definitions operations#
== Builtins
#TODO: builtins#
=== Builtin functions
=== [blue]_import()_
[blue]_import([grey]#<source-file>#)_ loads the multi-expression contained in the specified source and returns its value.
+94
View File
@@ -0,0 +1,94 @@
<mxfile host="app.diagrams.net" modified="2024-04-03T13:08:06.112Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0" etag="aiwjz2Mg7uovMJtfXvHm" version="24.2.1" type="device">
<diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
<mxGraphModel dx="989" dy="570" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-0" />
<mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0" />
<mxCell id="q_7kt0Q1lEUOPZf56y0L-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endArrow=classic;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-3" target="WIyWlLk6GJQsqaUBKTNV-7">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-3" value="scanner" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="180" y="39" width="100" height="40" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-14" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.13;entryY=0.475;entryDx=0;entryDy=0;endArrow=classic;endFill=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-7" target="q_7kt0Q1lEUOPZf56y0L-15">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="WIyWlLk6GJQsqaUBKTNV-7" value="" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
<mxGeometry x="320" y="14" width="286" height="90" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-5" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-0" target="WIyWlLk6GJQsqaUBKTNV-3">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-0" value="input stream" style="shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="30" y="39" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-11" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-2" target="q_7kt0Q1lEUOPZf56y0L-3">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-2" value="&lt;div&gt;syntax&lt;/div&gt;&lt;div&gt;analyzer&lt;br&gt;&lt;/div&gt;" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="356" y="39" width="90" height="40" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-3" value="&lt;div&gt;static semanthic&lt;/div&gt;&lt;div&gt;analyzer&lt;br&gt;&lt;/div&gt;" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="477" y="39" width="100" height="40" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-4" value="Parser" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="433" y="10" width="60" height="30" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-10" value="" style="endArrow=classic;html=1;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="WIyWlLk6GJQsqaUBKTNV-7" target="q_7kt0Q1lEUOPZf56y0L-2">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="356" y="80" as="sourcePoint" />
<mxPoint x="406" y="30" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-12" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-3" target="WIyWlLk6GJQsqaUBKTNV-7">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="356" y="80" as="sourcePoint" />
<mxPoint x="406" y="30" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-30" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-13" target="q_7kt0Q1lEUOPZf56y0L-29">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-13" value="" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="785" y="12" width="255" height="95" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-16" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-15" target="q_7kt0Q1lEUOPZf56y0L-13">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-15" value="AST" style="shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="634" y="40" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-18" value="&lt;div&gt;semanthic&lt;/div&gt;&lt;div&gt;analyzer&lt;br&gt;&lt;/div&gt;" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="814" y="40.5" width="100" height="40" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-19" value="Eval" style="rounded=1;whiteSpace=wrap;html=1;fontSize=12;glass=0;strokeWidth=1;shadow=0;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="946" y="40.5" width="60" height="40" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-22" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" target="q_7kt0Q1lEUOPZf56y0L-18">
<mxGeometry relative="1" as="geometry">
<mxPoint x="781" y="60" as="sourcePoint" />
<mxPoint x="790" y="70" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-24" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=classic;endFill=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-18" target="q_7kt0Q1lEUOPZf56y0L-19">
<mxGeometry relative="1" as="geometry">
<mxPoint x="750" y="70" as="sourcePoint" />
<mxPoint x="790" y="70" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-27" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;endFill=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="q_7kt0Q1lEUOPZf56y0L-19" target="q_7kt0Q1lEUOPZf56y0L-13">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="1020" y="62" as="sourcePoint" />
<mxPoint x="1070" y="12" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-29" value="&lt;div&gt;Computed&lt;/div&gt;&lt;div&gt;Value&lt;/div&gt;" style="shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="1060" y="39.5" width="110" height="40" as="geometry" />
</mxCell>
<mxCell id="q_7kt0Q1lEUOPZf56y0L-31" value="Excecuter" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
<mxGeometry x="872.5" y="9" width="80" height="30" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

+85
View File
@@ -0,0 +1,85 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// expr_test.go
package expr
import (
"fmt"
"strings"
"testing"
)
func TestExpr(t *testing.T) {
type inputType struct {
source string
wantResult any
wantErr error
}
inputs := []inputType{
/* 1 */ {`0?{}`, nil, nil},
/* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
/* 3 */ {`f=openFile("test-file.txt"); line=readFile(f); closeFile(f); line`, "uno", nil},
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
}
succeeded := 0
failed := 0
inputs1 := []inputType{
/* 1 */ {`
ds={
"init":func(end){@end=end; @current=0 but true},
"current":func(){current},
"next":func(){
((next=current+1) <= end) ? [true] {@current=next but current} :: {nil}
}
};
it=$(ds,3);
it++;
it++
`, int64(1), nil},
}
for i, input := range inputs1 {
var expr Expr
var gotResult any
var gotErr error
ctx := NewSimpleFuncStore()
// ImportMathFuncs(ctx)
// ImportImportFunc(ctx)
ImportOsFuncs(ctx)
parser := NewParser(ctx)
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good := true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
if gotResult != input.wantResult {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
good = false
}
}
if good {
succeeded++
} else {
failed++
}
}
t.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed))
}
+59
View File
@@ -0,0 +1,59 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-builtins.go
package expr
import (
"math"
"strconv"
)
func isNilFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if len(args) == 1 {
result = args[0] == nil
} else {
err = errOneParam(name)
}
return
}
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
if len(args) == 1 {
switch v := args[0].(type) {
case int64:
result = v
case float64:
result = int64(math.Trunc(v))
case bool:
if v {
result = int64(1)
} else {
result = int64(0)
}
case string:
var i int
if i, err = strconv.Atoi(v); err == nil {
result = int64(i)
}
default:
err = errCantConvert(name, v, "int")
}
} else {
err = errOneParam(name)
}
return
}
func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err error) {
return
}
func ImportBuiltinsFuncs(ctx ExprContext) {
ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, -1)
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, -1)
}
func init() {
registerImport("builtins", ImportBuiltinsFuncs)
}
+17
View File
@@ -0,0 +1,17 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-common.go
package expr
import (
"fmt"
)
func errOneParam(funcName string) error {
return fmt.Errorf("%s() requires exactly one param", funcName)
}
func errCantConvert(funcName string, value any, kind string) error {
return fmt.Errorf("%s() can't convert %T to %s", funcName, value, kind)
}
+146
View File
@@ -0,0 +1,146 @@
// 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 includeFunc(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, NewFlatArrayIterator(args))
return
}
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(isString(paramValue) /*|| isList(paramValue)*/) {
err = fmt.Errorf("%s(): param nr %d has wrong type %T, string expected", funcName, paramPos+1, paramValue)
}
return
}
func addEnvImportDirs(dirList []string) []string {
if dirSpec, exists := os.LookupEnv(ENV_EXPR_PATH); exists {
dirs := strings.Split(dirSpec, ":")
if dirList == nil {
dirList = dirs
} else {
dirList = append(dirList, dirs...)
}
}
return dirList
}
func addPresetImportDirs(ctx ExprContext, dirList []string) []string {
if dirSpec, exists := getControlString(ctx, ControlImportPath); exists {
dirs := strings.Split(dirSpec, ":")
if dirList == nil {
dirList = dirs
} else {
dirList = append(dirList, dirs...)
}
}
return dirList
}
func isFile(filePath string) bool {
info, err := os.Stat(filePath)
return (err == nil || errors.Is(err, os.ErrExist)) && info.Mode().IsRegular()
}
func searchAmongPath(filename string, dirList []string) (filePath string) {
for _, dir := range dirList {
if fullPath := path.Join(dir, filename); isFile(fullPath) {
filePath = fullPath
break
}
}
return
}
func isPathRelative(filePath string) bool {
unixPath := filepath.ToSlash(filePath)
return strings.HasPrefix(unixPath, "./") || strings.HasPrefix(unixPath, "../")
}
func makeFilepath(filename string, dirList []string) (filePath string, err error) {
if path.IsAbs(filename) || isPathRelative(filename) {
if isFile(filename) {
filePath = filename
}
} else {
filePath = searchAmongPath(filename, dirList)
}
if len(filePath) == 0 {
err = fmt.Errorf("source file %q not found", filename)
}
return
}
func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (result any, err error) {
var v any
var sourceFilepath string
for v, err = it.Next(); err == nil; v, err = it.Next() {
if err = checkStringParamExpected(name, v, it.Index()); err != nil {
break
}
if sourceFilepath, err = makeFilepath(v.(string), dirList); err != nil {
break
}
var file *os.File
if file, err = os.Open(sourceFilepath); err == nil {
defer file.Close()
var expr *ast
scanner := NewScanner(file, DefaultTranslations())
parser := NewParser(ctx)
if expr, err = parser.parseGeneral(scanner, true, true); err == nil {
result, err = expr.eval(ctx, false)
}
if err != nil {
break
}
} else {
break
}
}
if err != nil {
if err == io.EOF {
err = nil
} else {
result = nil
}
}
return
}
func ImportImportFuncs(ctx ExprContext) {
ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1)
ctx.RegisterFunc("include", &simpleFunctor{f: includeFunc}, 1, -1)
}
func init() {
registerImport("import", ImportImportFuncs)
}
+119
View File
@@ -0,0 +1,119 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// funcs-math.go
package expr
import (
"fmt"
"io"
)
func checkNumberParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(isNumber(paramValue) || isList(paramValue)) {
err = fmt.Errorf("%s(): param nr %d has wrong type %T, number expected", funcName, paramPos+1, paramValue)
}
return
}
func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
var sumAsFloat = false
var floatSum float64 = 0.0
var intSum int64 = 0
var v any
for v, err = it.Next(); err == nil; v, err = it.Next() {
if subIter, ok := v.(Iterator); ok {
if v, err = doAdd(ctx, name, subIter); err != nil {
break
}
} else {
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
break
}
if array, ok := v.([]any); ok {
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
break
}
}
}
if !sumAsFloat && isFloat(v) {
sumAsFloat = true
floatSum = float64(intSum)
}
if sumAsFloat {
floatSum += numAsFloat(v)
} else {
iv, _ := v.(int64)
intSum += iv
}
}
if err == nil || err == io.EOF {
err = nil
if sumAsFloat {
result = floatSum
} else {
result = intSum
}
}
return
}
func addFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result, err = doAdd(ctx, name, NewFlatArrayIterator(args))
return
}
func doMul(ctx ExprContext, name string, it Iterator) (result any, err error) {
var mulAsFloat = false
var floatProd float64 = 1.0
var intProd int64 = 1
var v any
for v, err = it.Next(); err == nil; v, err = it.Next() {
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
break
}
if array, ok := v.([]any); ok {
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
break
}
}
if !mulAsFloat && isFloat(v) {
mulAsFloat = true
floatProd = float64(intProd)
}
if mulAsFloat {
floatProd *= numAsFloat(v)
} else {
iv, _ := v.(int64)
intProd *= iv
}
}
if err == nil || err == io.EOF {
err = nil
if mulAsFloat {
result = floatProd
} else {
result = intProd
}
}
return
}
func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result, err = doMul(ctx, name, NewFlatArrayIterator(args))
return
}
func ImportMathFuncs(ctx ExprContext) {
ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
}
func init() {
registerImport("math.arith", ImportMathFuncs)
}
+168
View File
@@ -0,0 +1,168 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-os.go
package expr
import (
"bufio"
"fmt"
"io"
"os"
)
type osHandle interface {
getFile() *os.File
}
type osWriter struct {
fh *os.File
writer *bufio.Writer
}
func (h *osWriter) getFile() *os.File {
return h.fh
}
type osReader struct {
fh *os.File
reader *bufio.Reader
}
func (h *osReader) getFile() *os.File {
return h.fh
}
func createFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var filePath string
if len(args) > 0 {
filePath, _ = args[0].(string)
}
if len(filePath) > 0 {
var fh *os.File
if fh, err = os.Create(filePath); err == nil {
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
}
} else {
err = fmt.Errorf("%s(): missing the file path", name)
}
return
}
func openFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var filePath string
if len(args) > 0 {
filePath, _ = args[0].(string)
}
if len(filePath) > 0 {
var fh *os.File
if fh, err = os.Open(filePath); err == nil {
result = &osReader{fh: fh, reader: bufio.NewReader(fh)}
}
} else {
err = fmt.Errorf("%s(): missing the file path", name)
}
return
}
func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var filePath string
if len(args) > 0 {
filePath, _ = args[0].(string)
}
if len(filePath) > 0 {
var fh *os.File
if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil {
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
}
} else {
err = fmt.Errorf("%s(): missing the file path", name)
}
return
}
func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle
if len(args) > 0 {
handle, _ = args[0].(osHandle)
}
if handle != nil {
if fh := handle.getFile(); fh != nil {
if w, ok := handle.(*osWriter); ok {
err = w.writer.Flush()
}
if err == nil {
err = fh.Close()
}
}
} else {
err = fmt.Errorf("%s(): invalid file handle", name)
}
result = err == nil
return
}
func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle
if len(args) > 0 {
handle, _ = args[0].(osHandle)
}
if handle != nil {
if fh := handle.getFile(); fh != nil {
if w, ok := handle.(*osWriter); ok {
result, err = fmt.Fprint(w.writer, args[1:]...)
}
}
}
return
}
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle
if len(args) > 0 {
handle, _ = args[0].(osHandle)
}
if handle != nil {
if fh := handle.getFile(); fh != nil {
if r, ok := handle.(*osReader); ok {
var limit byte = '\n'
var v string
if len(args) > 1 {
if s, ok := args[1].(string); ok && len(s) > 0 {
limit = s[0]
}
}
if v, err = r.reader.ReadString(limit); err == nil || err == io.EOF {
if len(v) > 0 && v[len(v)-1] == limit {
result = v[0 : len(v)-1]
} else {
result = v
}
}
}
}
}
return
}
func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("openFile", &simpleFunctor{f: openFileFunc}, 1, 1)
ctx.RegisterFunc("appendFile", &simpleFunctor{f: appendFileFunc}, 1, 1)
ctx.RegisterFunc("createFile", &simpleFunctor{f: createFileFunc}, 1, 1)
ctx.RegisterFunc("writeFile", &simpleFunctor{f: writeFileFunc}, 1, -1)
ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2)
ctx.RegisterFunc("closeFile", &simpleFunctor{f: closeFileFunc}, 1, 1)
}
func init() {
registerImport("os", ImportOsFuncs)
}
+82
View File
@@ -0,0 +1,82 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// funcs_test.go
package expr
import (
"errors"
"fmt"
"strings"
"testing"
)
func TestFuncs(t *testing.T) {
type inputType struct {
source string
wantResult any
wantErr error
}
inputs := []inputType{
/* 1 */ {`isNil(nil)`, true, nil},
/* 2 */ {`v=nil; isNil(v)`, true, nil},
/* 3 */ {`v=5; isNil(v)`, false, nil},
/* 4 */ {`int(true)`, int64(1), nil},
/* 5 */ {`int(false)`, int64(0), nil},
/* 6 */ {`int(3.1)`, int64(3), nil},
/* 7 */ {`int(3.9)`, int64(3), nil},
/* 8 */ {`int("432")`, int64(432), nil},
/* 9 */ {`int("1.5")`, nil, errors.New(`strconv.Atoi: parsing "1.5": invalid syntax`)},
/* 10 */ {`int("432", 4)`, nil, errors.New(`int() requires exactly one param`)},
/* 11 */ {`int(nil)`, nil, errors.New(`int() can't convert <nil> to int`)},
}
succeeded := 0
failed := 0
// inputs1 := []inputType{
// /* 1 */ {`0?{}`, nil, nil},
// }
for i, input := range inputs {
var expr Expr
var gotResult any
var gotErr error
ctx := NewSimpleFuncStore()
// ImportMathFuncs(ctx)
// ImportImportFunc(ctx)
// ImportOsFuncs(ctx)
parser := NewParser(ctx)
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good := true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
if gotResult != input.wantResult {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
good = false
}
}
if good {
succeeded++
} else {
failed++
}
}
t.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed))
}
+47
View File
@@ -0,0 +1,47 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// function-register.go
package expr
import (
"path/filepath"
)
var functionRegister map[string]func(ExprContext)
func registerImport(name string, importFunc func(ExprContext)) {
if functionRegister == nil {
functionRegister = make(map[string]func(ExprContext))
}
functionRegister[name] = importFunc
}
func ImportInContext(ctx ExprContext, name string) (exists bool) {
var importFunc func(ExprContext)
if importFunc, exists = functionRegister[name]; exists {
importFunc(ctx)
}
return
}
func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) {
var matched bool
for name, importFunc := range functionRegister {
if matched, err = filepath.Match(pattern, name); err == nil {
if matched {
count++
importFunc(ctx)
}
} else {
break
}
}
return
}
func init() {
if functionRegister == nil {
functionRegister = make(map[string]func(ExprContext))
}
}
+3
View File
@@ -0,0 +1,3 @@
module git.portale-stac.it/go-pkg/expr
go 1.21.6
+122
View File
@@ -0,0 +1,122 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// graph.go
package expr
import (
"fmt"
"strings"
)
func BuildGraph(root *term) (g string) {
//var sb strings.Builder
g = fmt.Sprintf("tree: %v", root)
r := NewReticle(root)
fmt.Println(r)
return
}
type NodeRef struct {
node *term
pos int
label string
}
type Level []*NodeRef
type Reticle struct {
levels []Level
left, right int
colsWidth []int
}
func NewExprReticle(e Expr) *Reticle {
tree, _ := e.(*ast)
return NewReticle(tree.root)
}
func NewReticle(tree *term) *Reticle {
r := &Reticle{levels: make([]Level, 0)}
r.build(tree, 0, 0)
r.computeCharWidth()
return r
}
func (r *Reticle) computeCharWidth() {
numCol := r.right - r.left + 1
r.colsWidth = make([]int, numCol)
for _, level := range r.levels {
for _, ref := range level {
c := ref.pos - r.left
if v := ref.node.value(); v != nil {
ref.label = fmt.Sprintf("%v", v)
} else {
ref.label = ref.node.source()
}
r.colsWidth[c] = max(r.colsWidth[c], len(ref.label)+2) // +2 to make room for brakets
}
}
}
func (r *Reticle) build(node *term, depth, pos int) {
var level Level
if node == nil {
return
}
if len(r.levels) == depth {
level = make(Level, 0)
r.levels = append(r.levels, level)
} else {
level = r.levels[depth]
}
ref := &NodeRef{node: node, pos: pos}
level = append(level, ref)
if r.left > pos {
r.left = pos
}
if r.right < pos {
r.right = pos
}
if node.children != nil {
halfPos := len(node.children) / 2
childPos := pos - halfPos - 1
for _, child := range node.children {
childPos++
if childPos == pos && (len(node.children)&1) == 0 {
childPos++
}
r.build(child, depth+1, childPos)
}
}
r.levels[depth] = level
}
func (r *Reticle) nodeInLevel(level []*NodeRef, index int) (node *NodeRef) {
for _, ref := range level {
if ref.pos-r.left == index {
node = ref
break
}
}
return
}
func (r *Reticle) String() string {
var sb strings.Builder
for _, level := range r.levels {
for j, w := range r.colsWidth {
var label string
if ref := r.nodeInLevel(level, j); ref != nil {
s := "(" + ref.label + ")"
label = fmt.Sprintf("%[1]*s", -w, fmt.Sprintf("%[1]*s", (w+len(s))/2, s))
} else {
label = strings.Repeat(" ", w)
}
sb.WriteString(label)
}
sb.WriteByte('\n')
}
return sb.String()
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// graph_test.go
package expr
import (
"fmt"
"testing"
)
func TestGraph(t *testing.T) {
// tk0 := NewToken(0, 0, SymChangeSign, "-")
tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
tk2 := NewToken(0, 0, SymPlus, "+")
tk3 := NewValueToken(0, 0, SymInteger, "200", 200)
tree := NewAst()
// tree.addToken(tk0)
tree.addToken(tk1)
tree.addToken(tk2)
tree.addToken(tk3)
tk4 := NewToken(0, 0, SymPlus, "-")
tk5 := NewValueToken(0, 0, SymInteger, "50", 50)
tree.addToken(tk4)
tree.addToken(tk5)
g := BuildGraph(tree.root)
fmt.Println(g)
}
+61
View File
@@ -0,0 +1,61 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// helpers.go
package expr
import (
"fmt"
"strings"
)
func EvalString(ctx ExprContext, source string) (result any, err error) {
var tree *ast
r := strings.NewReader(source)
scanner := NewScanner(r, DefaultTranslations())
parser := NewParser(ctx)
if tree, err = parser.Parse(scanner); err == nil {
result, err = tree.eval(ctx, true)
}
return
}
type Arg struct {
Name string
Value any
}
func EvalStringA(source string, args ...Arg) (result any, err error) {
return EvalStringV(source, args)
}
func EvalStringV(source string, args []Arg) (result any, err error) {
ctx := NewSimpleFuncStore()
for _, arg := range args {
if isFunc(arg.Value) {
if f, ok := arg.Value.(FuncTemplate); ok {
functor := &simpleFunctor{f: f}
ctx.RegisterFunc(arg.Name, functor, 0, -1)
} else {
err = fmt.Errorf("invalid function specification: %q", arg.Name)
}
} else if integer, ok := anyInteger(arg.Value); ok {
ctx.SetVar(arg.Name, integer)
} else if float, ok := anyFloat(arg.Value); ok {
ctx.SetVar(arg.Name, float)
} else if _, ok := arg.Value.(string); ok {
ctx.SetVar(arg.Name, arg.Value)
} else if _, ok := arg.Value.(bool); ok {
ctx.SetVar(arg.Name, arg.Value)
} else {
err = fmt.Errorf("unsupported type %T specified for item %q", arg.Value, arg.Name)
}
}
if err == nil {
result, err = EvalString(ctx, source)
}
return
}
+81
View File
@@ -0,0 +1,81 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// helpers_test.go
package expr
import (
"fmt"
"testing"
)
func subtract(ctx ExprContext, name string, args []any) (result any, err error) {
if len(args) != 2 {
err = fmt.Errorf("%s(): requires exactly two arguments", name)
return
}
x, xok := args[0].(int64)
y, yok := args[1].(int64)
if xok && yok {
result = x - y
} else {
err = fmt.Errorf("expected integer (int64) arguments, got %T and %T values", x, y)
}
return
}
func TestEvalStringA(t *testing.T) {
source := `a + b * subtract(4,2)`
args := []Arg{
{"a", uint8(1)},
{"b", int8(2)},
{"subtract", FuncTemplate(subtract)},
// force coverage
{"a16", uint16(1)},
{"b16", int16(2)},
{"a32", uint32(1)},
{"b32", int32(2)},
{"a64", uint64(1)},
{"b64", int64(2)},
{"f32", float32(1.0)},
{"f64", float64(1.0)},
}
wantResult := int64(5)
gotResult, gotErr := EvalStringA(source, args...)
if value, ok := gotResult.(int64); ok && value != wantResult {
t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult)
t.Errorf("Error: %v", gotErr)
}
}
func TestEvalString(t *testing.T) {
ctx := NewSimpleVarStore()
ctx.SetVar("a", uint8(1))
ctx.SetVar("b", int8(2))
ctx.SetVar("f", 2.0)
// force coverage
ctx.SetVar("a16", uint16(1))
ctx.SetVar("b16", int16(2))
ctx.SetVar("a32", uint32(1))
ctx.SetVar("b32", int32(2))
ctx.SetVar("a64", uint64(1))
ctx.SetVar("b64", int64(2))
ctx.SetVar("f32", float32(1.0))
ctx.SetVar("f64", float64(1.0))
// force coverage
ctx.GetFuncInfo("dummy")
ctx.Call("dummy", []any{})
source := `a + b * f`
wantResult := float64(5)
gotResult, gotErr := EvalString(ctx, source)
if value, ok := gotResult.(float64); ok && value != wantResult {
t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult)
t.Errorf("Error: %v", gotErr)
}
}
+58
View File
@@ -0,0 +1,58 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// it-range.go
package expr
import "io"
type RangeIterator struct {
start int64
end int64
step int64
current int64
index int
}
func NewRangeIterator(start, end, step int64) *RangeIterator {
if step == 0 {
panic("Range step must be not zero")
}
if step < 0 && start < end {
panic("When the range's step is less than zero, start must be greater than end")
}
if step > 0 && start > end {
panic("When the range's step is greater than zero, start must be less than end")
}
return &RangeIterator{start: start, end: end, step: step, current: start, index: 0}
}
func (it *RangeIterator) Reset() {
it.index = 0
it.current = it.start
}
func (it *RangeIterator) Next() (item any, err error) {
if it.step > 0 {
if it.current < it.end {
item = it.current
it.current += it.step
it.index++
} else {
err = io.EOF
}
} else {
if it.current > it.end {
item = it.current
it.current += it.step
it.index++
} else {
err = io.EOF
}
}
return
}
func (it *RangeIterator) Index() int {
return it.index - 1
}
+42
View File
@@ -0,0 +1,42 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// iter-list.go
package expr
import "io"
type FlatArrayIterator struct {
a []any
index int
}
func NewFlatArrayIterator(array []any) *FlatArrayIterator {
return &FlatArrayIterator{a: array, index: 0}
}
func (it *FlatArrayIterator) Current() (item any, err error) {
if it.index >= 0 && it.index < len(it.a) {
item = it.a[it.index]
} else {
err = io.EOF
}
return
}
func (it *FlatArrayIterator) Next() (item any, err error) {
if item, err = it.Current(); err != io.EOF {
it.index++
}
// if it.index < len(it.a) {
// item = it.a[it.index]
// it.index++
// } else {
// err = io.EOF
// }
return
}
func (it *FlatArrayIterator) Index() int {
return it.index - 1
}
+11
View File
@@ -0,0 +1,11 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// iterator.go
package expr
type Iterator interface {
Next() (item any, err error) // must return io.EOF after the last item
Current() (item any, err error)
Index() int
}
+11 -51
View File
@@ -1,54 +1,13 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-const.go
package expr
// -------- bool const term
func newBoolTerm(tk *Token) *term {
// -------- const term
func newConstTerm(tk *Token) *term {
return &term{
tk: *tk,
class: classConst,
kind: kindBool,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalConst,
}
}
// -------- integer const term
func newIntegerTerm(tk *Token) *term {
return &term{
tk: *tk,
class: classConst,
kind: kindInteger,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalConst,
}
}
// -------- float const term
func newFloatTerm(tk *Token) *term {
return &term{
tk: *tk,
class: classConst,
kind: kindFloat,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalConst,
}
}
// -------- string const term
func newStringTerm(tk *Token) *term {
return &term{
tk: *tk,
class: classConst,
kind: kindString,
parent: nil,
children: nil,
position: posLeaf,
@@ -58,15 +17,16 @@ func newStringTerm(tk *Token) *term {
}
// -------- eval func
func evalConst(ctx exprContext, self *term) (v any, err error) {
func evalConst(ctx ExprContext, self *term) (v any, err error) {
v = self.tk.Value
return
}
// init
func init() {
registerTermConstructor(SymString, newStringTerm)
registerTermConstructor(SymInteger, newIntegerTerm)
registerTermConstructor(SymFloat, newFloatTerm)
registerTermConstructor(SymBool, newBoolTerm)
registerTermConstructor(SymString, newConstTerm)
registerTermConstructor(SymInteger, newConstTerm)
registerTermConstructor(SymFloat, newConstTerm)
registerTermConstructor(SymBool, newConstTerm)
registerTermConstructor(SymKwNil, newConstTerm)
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-dict.go
package expr
// -------- dict term
func newDictTerm(args map[any]*term) *term {
return &term{
tk: *NewValueToken(0, 0, SymDict, "{}", args),
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalDict,
}
}
// -------- dict func
func evalDict(ctx ExprContext, self *term) (v any, err error) {
dict, _ := self.value().(map[any]*term)
items := make(map[any]any, len(dict))
for key, tree := range dict {
var param any
if param, err = tree.compute(ctx); err != nil {
break
}
items[key] = param
}
if err == nil {
v = items
}
return
}
+29
View File
@@ -0,0 +1,29 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-expr.go
package expr
import "fmt"
// -------- expr term
func newExprTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalExpr,
}
}
// -------- eval expr
func evalExpr(ctx ExprContext, self *term) (v any, err error) {
if expr, ok := self.value().(Expr); ok {
v, err = expr.eval(ctx, false)
} else {
err = fmt.Errorf("expression expected, got %T", self.value())
}
return
}
+69 -8
View File
@@ -1,22 +1,28 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-func.go
package expr
// -------- function term
func newFuncTerm(tk *Token, args []*term) *term {
import (
"errors"
)
// -------- function call term
func newFuncCallTerm(tk *Token, args []*term) *term {
return &term{
tk: *tk,
class: classVar,
kind: kindUnknown,
parent: nil,
children: args,
position: posLeaf,
priority: priValue,
evalFunc: evalFunc,
evalFunc: evalFuncCall,
}
}
// -------- eval func
func evalFunc(ctx exprContext, self *term) (v any, err error) {
// -------- eval func call
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
ctx := cloneContext(parentCtx)
name, _ := self.tk.Value.(string)
params := make([]any, len(self.children))
for i, tree := range self.children {
@@ -27,7 +33,62 @@ func evalFunc(ctx exprContext, self *term) (v any, err error) {
params[i] = param
}
if err == nil {
v, err = ctx.Call(name, params)
if v, err = ctx.Call(name, params); err == nil {
exportObjects(parentCtx, ctx)
}
}
return
}
// -------- function definition term
func newFuncDefTerm(tk *Token, args []*term) *term {
return &term{
tk: *tk,
parent: nil,
children: args, // arg[0]=formal-param-list, arg[1]=*ast
position: posLeaf,
priority: priValue,
evalFunc: evalFuncDef,
}
}
// -------- eval func def
// TODO
type funcDefFunctor struct {
params []string
expr Expr
}
func (functor *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
for i, p := range functor.params {
if i < len(args) {
arg := args[i]
if functor, ok := arg.(Functor); ok {
ctx.RegisterFunc(p, functor, 0, -1)
} else {
ctx.setVar(p, arg)
}
} else {
ctx.setVar(p, nil)
}
}
result, err = functor.expr.eval(ctx, false)
return
}
func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
bodySpec := self.value()
if expr, ok := bodySpec.(*ast); ok {
paramList := make([]string, 0, len(self.children))
for _, param := range self.children {
paramList = append(paramList, param.source())
}
v = &funcDefFunctor{
params: paramList,
expr: expr,
}
} else {
err = errors.New("invalid function definition: the body specification must be an expression")
}
return
}
+104
View File
@@ -0,0 +1,104 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-iterator.go
package expr
import (
"fmt"
)
// -------- iterator term
func newIteratorTerm(tk *Token, dsTerm *term, args []*term) *term {
tk.Sym = SymIterator
children := make([]*term, 0, 1+len(args))
children = append(children, dsTerm)
children = append(children, args...)
return &term{
tk: *tk,
parent: nil,
children: children,
position: posLeaf,
priority: priValue,
evalFunc: evalIterator,
}
}
// -------- eval iterator
func evalTermArray(ctx ExprContext, a []*term) (values []any, err error) {
values = make([]any, len(a))
for i, t := range a {
var value any
if value, err = t.compute(ctx); err == nil {
values[i] = value
} else {
break
}
}
return
}
func getDataSourceDict(ctx ExprContext, self *term) (ds map[string]Functor, err error) {
var value any
if len(self.children) < 1 || self.children[0] == nil {
err = self.Errorf("missing the data-source parameter")
return
}
if value, err = self.children[0].compute(ctx); err != nil {
return
}
if dictAny, ok := value.(map[any]any); ok {
ds = make(map[string]Functor)
for _, k := range []string{initName, currentName, nextName} {
if item, exists := dictAny[k]; exists && item != nil {
if functor, ok := item.(*funcDefFunctor); ok {
ds[k] = functor
}
} else if k != initName {
err = fmt.Errorf("the data-source must provide a non-nil %q operator", k)
break
}
}
} else {
err = self.Errorf("the first param (data-source) of an iterator must be a dict, not a %T", value)
}
return
}
func evalIterator(ctx ExprContext, self *term) (v any, err error) {
var ds map[string]Functor
if ds, err = getDataSourceDict(ctx, self); err != nil {
return
}
dc := newDataCursor(ctx)
if initFunc, exists := ds[initName]; exists && initFunc != nil {
var args []any
if len(self.children) > 1 {
if args, err = evalTermArray(ctx, self.children[1:]); err != nil {
return
}
} else {
args = []any{}
}
initCtx := dc.ctx.Clone()
if dc.resource, err = initFunc.Invoke(initCtx, initName, args); err != nil {
return
}
exportObjects(dc.ctx, initCtx)
}
dc.nextFunc, _ = ds[nextName]
dc.currentFunc, _ = ds[currentName]
v = dc
return
}
+66
View File
@@ -0,0 +1,66 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-list.go
package expr
// -------- list term
func newListTermA(args ...*term) *term {
return newListTerm(args)
}
func newListTerm(args []*term) *term {
return &term{
tk: *NewValueToken(0, 0, SymList, "[]", args),
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalList,
}
}
// -------- list func
func evalList(ctx ExprContext, self *term) (v any, err error) {
list, _ := self.value().([]*term)
items := make([]any, len(list))
for i, tree := range list {
var param any
if param, err = tree.compute(ctx); err != nil {
break
}
items[i] = param
}
if err == nil {
v = items
}
return
}
// // -------- list term
// func newListTerm(args []*term) *term {
// return &term{
// tk: *NewToken(0, 0, SymList, "[]"),
// parent: nil,
// children: args,
// position: posLeaf,
// priority: priValue,
// evalFunc: evalList,
// }
// }
// // -------- list func
// func evalList(ctx ExprContext, self *term) (v any, err error) {
// items := make([]any, len(self.children))
// for i, tree := range self.children {
// var param any
// if param, err = tree.compute(ctx); err != nil {
// break
// }
// items[i] = param
// }
// if err == nil {
// v = items
// }
// return
// }
+50
View File
@@ -0,0 +1,50 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-selector-case.go
package expr
import (
"fmt"
"strings"
)
// -------- selector case term
type selectorCase struct {
filterList *term
caseExpr Expr
}
func (sc *selectorCase) String() string {
var sb strings.Builder
if sc.filterList != nil {
sc.filterList.toString(&sb)
sb.WriteByte(' ')
}
sb.WriteByte('{')
sb.WriteString(sc.caseExpr.String())
sb.WriteByte('}')
return sb.String()
}
func newSelectorCaseTerm(row, col int, filterList *term, caseExpr Expr) *term {
tk := NewValueToken(row, col, SymSelectorCase, "", &selectorCase{filterList: filterList, caseExpr: caseExpr})
return &term{
tk: *tk,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalSelectorCase,
}
}
// -------- eval selector case
func evalSelectorCase(ctx ExprContext, self *term) (v any, err error) {
var ok bool
if v, ok = self.value().(*selectorCase); !ok {
err = fmt.Errorf("selector-case expected, got %T", self.value())
}
return
}
+14 -6
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-var.go
package expr
@@ -6,9 +9,9 @@ import "fmt"
// -------- variable term
func newVarTerm(tk *Token) *term {
return &term{
tk: *tk,
class: classVar,
kind: kindUnknown,
tk: *tk,
// class: classVar,
// kind: kindUnknown,
parent: nil,
children: nil,
position: posLeaf,
@@ -18,10 +21,15 @@ func newVarTerm(tk *Token) *term {
}
// -------- eval func
func evalVar(ctx exprContext, self *term) (v any, err error) {
func evalVar(ctx ExprContext, self *term) (v any, err error) {
var exists bool
if v, exists = ctx.GetValue(self.tk.source); !exists {
err = fmt.Errorf("undefined variable %q", self.tk.source)
name := self.source()
if v, exists = ctx.GetVar(name); !exists {
if info, exists := ctx.GetFuncInfo(name); exists {
v = info.Functor()
} else {
err = fmt.Errorf("undefined variable or function %q", name)
}
}
return
}
+43
View File
@@ -0,0 +1,43 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-assign.go
package expr
//-------- assign term
func newAssignTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priAssign,
evalFunc: evalAssign,
}
}
func evalAssign(ctx ExprContext, self *term) (v any, err error) {
if err = self.checkOperands(); err != nil {
return
}
leftTerm := self.children[0]
if leftTerm.tk.Sym != SymIdentifier {
err = leftTerm.tk.Errorf("left operand of %q must be a variable", self.tk.source)
return
}
if v, err = self.children[1].compute(ctx); err == nil {
if functor, ok := v.(Functor); ok {
ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
} else {
ctx.setVar(leftTerm.source(), v)
}
}
return
}
// init
func init() {
registerTermConstructor(SymEqual, newAssignTerm)
}
+84 -11
View File
@@ -1,13 +1,16 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-bool.go
package expr
import "fmt"
//-------- NOT term
func newNotTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priNot,
@@ -15,7 +18,7 @@ func newNotTerm(tk *Token) (inst *term) {
}
}
func evalNot(ctx exprContext, self *term) (v any, err error) {
func evalNot(ctx ExprContext, self *term) (v any, err error) {
var rightValue any
if rightValue, err = self.evalPrefix(ctx); err != nil {
@@ -34,9 +37,9 @@ func evalNot(ctx exprContext, self *term) (v any, err error) {
func newAndTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
tk: *tk,
// class: classOperator,
// kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priAnd,
@@ -44,7 +47,16 @@ 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) {
v, err = evalAndWithShortcut(ctx, self)
} else {
v, err = evalAndWithoutShortcut(ctx, self)
}
return
}
func evalAndWithoutShortcut(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
var leftBool, rightBool bool
var lok, rok bool
@@ -64,13 +76,39 @@ func evalAnd(ctx exprContext, self *term) (v any, err error) {
return
}
func evalAndWithShortcut(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if err = self.checkOperands(); err != nil {
return
}
if leftValue, err = self.children[0].compute(ctx); err != nil {
return
}
if leftBool, lok := toBool(leftValue); !lok {
err = fmt.Errorf("got %T as left operand type of 'and' operator, it must be bool", leftBool)
return
} else if !leftBool {
v = false
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
if rightBool, rok := toBool(rightValue); rok {
v = rightBool
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
}
return
}
//-------- OR term
func newOrTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
tk: *tk,
// class: classOperator,
// kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priOr,
@@ -78,7 +116,16 @@ 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) {
v, err = evalOrWithShortcut(ctx, self)
} else {
v, err = evalOrWithoutShortcut(ctx, self)
}
return
}
func evalOrWithoutShortcut(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
var leftBool, rightBool bool
var lok, rok bool
@@ -98,6 +145,32 @@ func evalOr(ctx exprContext, self *term) (v any, err error) {
return
}
func evalOrWithShortcut(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if err = self.checkOperands(); err != nil {
return
}
if leftValue, err = self.children[0].compute(ctx); err != nil {
return
}
if leftBool, lok := toBool(leftValue); !lok {
err = fmt.Errorf("got %T as left operand type of 'or' operator, it must be bool", leftBool)
return
} else if leftBool {
v = true
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
if rightBool, rok := toBool(rightValue); rok {
v = rightBool
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
}
return
}
// init
func init() {
registerTermConstructor(SymNot, newNotTerm)
+57
View File
@@ -0,0 +1,57 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-length.go
package expr
//-------- builtin term
func newBuiltinTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priSign,
evalFunc: evalBuiltin,
}
}
func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
var rightValue any
if rightValue, err = self.evalPrefix(ctx); err != nil {
return
}
count := 0
if isList(rightValue) {
list, _ := rightValue.([]any)
for i, moduleSpec := range list {
if module, ok := moduleSpec.(string); ok {
if ImportInContext(ctx, module) {
count++
} else {
err = self.Errorf("unknown module %q", module)
break
}
} else {
err = self.Errorf("expected string at item nr %d, got %T", i+1, moduleSpec)
break
}
}
} else if isString(rightValue) {
module, _ := rightValue.(string)
count, err = ImportInContextByGlobPattern(ctx, module)
} else {
err = self.errIncompatibleType(rightValue)
}
if err == nil {
v = count
}
return
}
// init
func init() {
registerTermConstructor(SymKwBuiltin, newBuiltinTerm)
}
+27
View File
@@ -0,0 +1,27 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-but.go
package expr
//-------- but term
func newButTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priBut,
evalFunc: evalBut,
}
}
func evalBut(ctx ExprContext, self *term) (v any, err error) {
_, v, err = self.evalInfix(ctx)
return
}
// init
func init() {
registerTermConstructor(SymKwBut, newButTerm)
}
+93
View File
@@ -0,0 +1,93 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-coalesce.go
package expr
//-------- null coalesce term
func newNullCoalesceTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
// class: classOperator,
// kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priCoalesce,
evalFunc: evalNullCoalesce,
}
}
func evalNullCoalesce(ctx ExprContext, self *term) (v any, err error) {
var rightValue any
if err = self.checkOperands(); err != nil {
return
}
leftTerm := self.children[0]
if leftTerm.tk.Sym != SymIdentifier {
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
return
}
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
v = leftValue
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
// if _, ok := rightValue.(Functor); ok {
// err = errCoalesceNoFunc(self.children[1])
// } else {
v = rightValue
// }
}
return
}
//-------- coalesce assign term
func newCoalesceAssignTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priCoalesce,
evalFunc: evalAssignCoalesce,
}
}
func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
var rightValue any
if err = self.checkOperands(); err != nil {
return
}
leftTerm := self.children[0]
if leftTerm.tk.Sym != SymIdentifier {
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
return
}
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
v = leftValue
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
if functor, ok := rightValue.(Functor); ok {
ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
} else {
v = rightValue
ctx.setVar(leftTerm.source(), rightValue)
}
}
return
}
// utils
// func errCoalesceNoFunc(t *term) error {
// return t.Errorf("the right operand of a coalescing operation cannot be a function definition")
// }
// init
func init() {
registerTermConstructor(SymDoubleQuestion, newNullCoalesceTerm)
registerTermConstructor(SymQuestionEqual, newCoalesceAssignTerm)
}
+49
View File
@@ -0,0 +1,49 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-context-value.go
package expr
//-------- context term
func newContextTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priPrePost,
evalFunc: evalContextValue,
}
}
func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
var childValue any
var sourceCtx ExprContext
if len(self.children) == 0 {
sourceCtx = ctx
} else if childValue, err = self.evalPrefix(ctx); err == nil {
if dc, ok := childValue.(*dataCursor); ok {
sourceCtx = dc.ctx
}
} else {
return
}
if sourceCtx != nil {
keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
d := make(map[string]any)
for _, key := range keys {
d[key], _ = sourceCtx.GetVar(key)
}
v = d
} else {
err = self.errIncompatibleType(childValue)
}
return
}
// init
func init() {
registerTermConstructor(SymDoubleDollar, newContextTerm)
}
+27
View File
@@ -0,0 +1,27 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-ctrl.go
package expr
//-------- export all term
func newExportAllTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalExportAll,
}
}
func evalExportAll(ctx ExprContext, self *term) (v any, err error) {
enable(ctx, control_export_all)
return
}
// init
func init() {
registerTermConstructor(SymDoubleAt, newExportAllTerm)
}
+72
View File
@@ -0,0 +1,72 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-dot.go
package expr
import "fmt"
// -------- dot term
func newDotTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priDot,
evalFunc: evalDot,
}
}
func evalDot(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
indexTerm := self.children[1]
if isList(leftValue) {
var index int
if index, err = indexTerm.toInt(rightValue, "index expression value must be integer"); err != nil {
return
}
list, _ := leftValue.([]any)
if index >= 0 && index < len(list) {
v = list[index]
} else if index >= -len(list) {
v = list[len(list)+index]
} else {
err = indexTerm.Errorf("index %v out of bounds", index)
}
} else if isString(leftValue) {
var index int
if index, err = indexTerm.toInt(rightValue, "index expression value must be integer"); err != nil {
return
}
s, _ := leftValue.(string)
if index >= 0 && index < len(s) {
v = string(s[index])
} else if index >= -len(s) {
v = string(s[len(s)+index])
} else {
err = indexTerm.Errorf("index %v out of bounds", index)
}
} else if isDict(leftValue) {
var ok bool
d, _ := leftValue.(map[any]any)
if v, ok = d[rightValue]; !ok {
err = fmt.Errorf("key %v does not belong to the dictionary", rightValue)
}
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
// init
func init() {
registerTermConstructor(SymDot, newDotTerm)
}
+4 -3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-fact.go
package expr
@@ -8,8 +11,6 @@ import "fmt"
func newFactTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindInteger,
children: make([]*term, 0, 1),
position: posPostfix,
priority: priFact,
@@ -17,7 +18,7 @@ func newFactTerm(tk *Token) (inst *term) {
}
}
func evalFact(ctx exprContext, self *term) (v any, err error) {
func evalFact(ctx ExprContext, self *term) (v any, err error) {
var leftValue any
if leftValue, err = self.evalPrefix(ctx); err != nil {
+65
View File
@@ -0,0 +1,65 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-insert.go
package expr
//-------- insert term
func newInsertTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priAssign,
evalFunc: evalInsert,
}
}
func newAppendTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priAssign,
evalFunc: evalAppend,
}
}
func evalInsert(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if isList(rightValue) {
list, _ := rightValue.([]any)
v = append([]any{leftValue}, list...)
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalAppend(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if isList(leftValue) {
list, _ := leftValue.([]any)
v = append(list, rightValue)
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
// init
func init() {
registerTermConstructor(SymInsert, newInsertTerm)
registerTermConstructor(SymAppend, newAppendTerm)
}
+38
View File
@@ -0,0 +1,38 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-iter-value.go
package expr
//-------- iter value term
func newIterValueTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priIterValue,
evalFunc: evalIterValue,
}
}
func evalIterValue(ctx ExprContext, self *term) (v any, err error) {
var leftValue any
if leftValue, err = self.evalPrefix(ctx); err != nil {
return
}
if dc, ok := leftValue.(*dataCursor); ok {
v, err = dc.Current()
} else {
err = self.errIncompatibleType(leftValue)
}
return
}
// init
func init() {
registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
}
+43
View File
@@ -0,0 +1,43 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-length.go
package expr
//-------- length term
func newLengthTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priSign,
evalFunc: evalLength,
}
}
func evalLength(ctx ExprContext, self *term) (v any, err error) {
var rightValue any
if rightValue, err = self.evalPrefix(ctx); err != nil {
return
}
if isList(rightValue) {
list, _ := rightValue.([]any)
v = len(list)
} else if isString(rightValue) {
s, _ := rightValue.(string)
v = len(s)
} else if it, ok := rightValue.(Iterator); ok {
v = it.Index()
} else {
err = self.errIncompatibleType(rightValue)
}
return
}
// init
func init() {
registerTermConstructor(SymHash, newLengthTerm)
}
+41
View File
@@ -0,0 +1,41 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-post-inc.go
package expr
// -------- post increment term
func newPostIncTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: make([]*term, 0, 1),
position: posPostfix,
priority: priPrePost,
evalFunc: evalPostInc,
}
}
func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
var leftValue any
if leftValue, err = self.evalPrefix(ctx); err != nil {
return
}
if dc, ok := leftValue.(*dataCursor); ok {
v, err = dc.Next()
} else if isInteger(leftValue) && self.children[0].symbol() == SymIdentifier {
v = leftValue
i, _ := leftValue.(int64)
ctx.SetVar(self.children[0].source(), i+1)
} else {
self.errIncompatibleType(leftValue)
}
return
}
// init
func init() {
registerTermConstructor(SymDoublePlus, newPostIncTerm)
}
+80 -8
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-prod.go
package expr
@@ -10,9 +13,9 @@ import (
func newMultiplyTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
tk: *tk,
// class: classOperator,
// kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priProduct,
@@ -20,7 +23,7 @@ func newMultiplyTerm(tk *Token) (inst *term) {
}
}
func evalMultiply(ctx exprContext, self *term) (v any, err error) {
func evalMultiply(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
@@ -49,9 +52,9 @@ func evalMultiply(ctx exprContext, self *term) (v any, err error) {
func newDivideTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
tk: *tk,
// class: classOperator,
// kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priProduct,
@@ -59,7 +62,7 @@ func newDivideTerm(tk *Token) (inst *term) {
}
}
func evalDivide(ctx exprContext, self *term) (v any, err error) {
func evalDivide(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
@@ -88,8 +91,77 @@ func evalDivide(ctx exprContext, self *term) (v any, err error) {
return
}
//-------- divide as float term
func newDivideAsFloatTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priProduct,
evalFunc: evalDivideAsFloat,
}
}
func evalDivideAsFloat(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if isNumber(leftValue) && isNumber(rightValue) {
d := numAsFloat(rightValue)
if d == 0.0 {
err = errors.New("division by zero")
} else {
v = numAsFloat(leftValue) / d
}
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
//-------- reminder term
func newReminderTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
// class: classOperator,
// kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priProduct,
evalFunc: evalReminder,
}
}
func evalReminder(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if isInteger(leftValue) && isInteger(rightValue) {
rightInt, _ := rightValue.(int64)
if rightInt == 0 {
err = errors.New("division by zero")
} else {
leftInt, _ := leftValue.(int64)
v = leftInt % rightInt
}
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
// init
func init() {
registerTermConstructor(SymStar, newMultiplyTerm)
registerTermConstructor(SymSlash, newDivideTerm)
registerTermConstructor(SymDotSlash, newDivideAsFloatTerm)
registerTermConstructor(SymPercent, newReminderTerm)
}
+24 -23
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-rel.go
package expr
@@ -5,9 +8,9 @@ package expr
func newEqualTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
tk: *tk,
// class: classOperator,
// kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
@@ -15,7 +18,7 @@ func newEqualTerm(tk *Token) (inst *term) {
}
}
func evalEqual(ctx exprContext, self *term) (v any, err error) {
func evalEqual(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
@@ -44,9 +47,9 @@ func evalEqual(ctx exprContext, self *term) (v any, err error) {
func newNotEqualTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
tk: *tk,
// class: classOperator,
// kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
@@ -54,7 +57,7 @@ func newNotEqualTerm(tk *Token) (inst *term) {
}
}
func evalNotEqual(ctx exprContext, self *term) (v any, err error) {
func evalNotEqual(ctx ExprContext, self *term) (v any, err error) {
if v, err = evalEqual(ctx, self); err == nil {
b, _ := toBool(v)
v = !b
@@ -91,9 +94,9 @@ func evalNotEqual(ctx exprContext, self *term) (v any, err error) {
func newLessTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
tk: *tk,
// class: classOperator,
// kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
@@ -101,7 +104,7 @@ func newLessTerm(tk *Token) (inst *term) {
}
}
func evalLess(ctx exprContext, self *term) (v any, err error) {
func evalLess(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
@@ -131,8 +134,6 @@ func evalLess(ctx exprContext, self *term) (v any, err error) {
func newLessEqualTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
@@ -140,7 +141,7 @@ func newLessEqualTerm(tk *Token) (inst *term) {
}
}
func evalLessEqual(ctx exprContext, self *term) (v any, err error) {
func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
@@ -169,9 +170,9 @@ func evalLessEqual(ctx exprContext, self *term) (v any, err error) {
func newGreaterTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
tk: *tk,
// class: classOperator,
// kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
@@ -179,7 +180,7 @@ func newGreaterTerm(tk *Token) (inst *term) {
}
}
func evalGreater(ctx exprContext, self *term) (v any, err error) {
func evalGreater(ctx ExprContext, self *term) (v any, err error) {
if v, err = evalLessEqual(ctx, self); err == nil {
b, _ := toBool(v)
v = !b
@@ -191,9 +192,9 @@ func evalGreater(ctx exprContext, self *term) (v any, err error) {
func newGreaterEqualTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
tk: *tk,
// class: classOperator,
// kind: kindBool,
children: make([]*term, 0, 2),
position: posInfix,
priority: priRelational,
@@ -201,7 +202,7 @@ func newGreaterEqualTerm(tk *Token) (inst *term) {
}
}
func evalGreaterEqual(ctx exprContext, self *term) (v any, err error) {
func evalGreaterEqual(ctx ExprContext, self *term) (v any, err error) {
if v, err = evalLess(ctx, self); err == nil {
b, _ := toBool(v)
v = !b
+71
View File
@@ -0,0 +1,71 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-selector.go
package expr
//-------- selector term
func newSelectorTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 3),
position: posInfix,
priority: priSelector,
evalFunc: evalSelector,
}
}
func trySelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (match bool, selectedValue any, err error) {
caseData, _ := caseSel.(*selectorCase)
if caseData.filterList == nil {
selectedValue, err = caseData.caseExpr.eval(ctx, false)
match = true
} else if filterList, ok := caseData.filterList.value().([]*term); ok {
if len(filterList) == 0 && exprValue == int64(caseIndex) {
selectedValue, err = caseData.caseExpr.eval(ctx, false)
match = true
} else {
var caseValue any
for _, caseTerm := range filterList {
if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue {
selectedValue, err = caseData.caseExpr.eval(ctx, false)
match = true
break
}
}
}
}
return
}
func evalSelector(ctx ExprContext, self *term) (v any, err error) {
var exprValue any
var match bool
if err = self.checkOperands(); err != nil {
return
}
exprTerm := self.children[0]
if exprValue, err = exprTerm.compute(ctx); err != nil {
return
}
caseListTerm := self.children[1]
caseList, _ := caseListTerm.value().([]*term)
for i, caseTerm := range caseList {
caseSel := caseTerm.value()
if match, v, err = trySelectorCase(ctx, exprValue, caseSel, i); err != nil || match {
break
}
}
if err == nil && !match {
err = exprTerm.tk.Errorf("no case catches the value (%v) of the selection expression", exprValue)
}
return
}
// init
func init() {
registerTermConstructor(SymSelector, newSelectorTerm)
}
+7 -6
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-sign.go
package expr
@@ -5,9 +8,9 @@ package expr
func newPlusSignTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
tk: *tk,
// class: classOperator,
// kind: kindUnknown,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priSign,
@@ -18,8 +21,6 @@ func newPlusSignTerm(tk *Token) (inst *term) {
func newMinusSignTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priSign,
@@ -27,7 +28,7 @@ func newMinusSignTerm(tk *Token) (inst *term) {
}
}
func evalSign(ctx exprContext, self *term) (v any, err error) {
func evalSign(ctx ExprContext, self *term) (v any, err error) {
var rightValue any
if rightValue, err = self.evalPrefix(ctx); err != nil {
+33 -6
View File
@@ -1,8 +1,12 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-sum.go
package expr
import (
"fmt"
"slices"
)
//-------- plus term
@@ -10,8 +14,6 @@ import (
func newPlusTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priSum,
@@ -19,7 +21,7 @@ func newPlusTerm(tk *Token) (inst *term) {
}
}
func evalPlus(ctx exprContext, self *term) (v any, err error) {
func evalPlus(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
@@ -36,6 +38,23 @@ func evalPlus(ctx exprContext, self *term) (v any, err error) {
rightInt, _ := rightValue.(int64)
v = leftInt + rightInt
}
} else if isList(leftValue) || isList(rightValue) {
var leftList, rightList []any
var ok bool
if leftList, ok = leftValue.([]any); !ok {
leftList = []any{leftValue}
}
if rightList, ok = rightValue.([]any); !ok {
rightList = []any{rightValue}
}
sumList := make([]any, 0, len(leftList)+len(rightList))
for _, item := range leftList {
sumList = append(sumList, item)
}
for _, item := range rightList {
sumList = append(sumList, item)
}
v = sumList
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
@@ -47,8 +66,6 @@ func evalPlus(ctx exprContext, self *term) (v any, err error) {
func newMinusTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priSum,
@@ -56,7 +73,7 @@ func newMinusTerm(tk *Token) (inst *term) {
}
}
func evalMinus(ctx exprContext, self *term) (v any, err error) {
func evalMinus(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
@@ -71,6 +88,16 @@ func evalMinus(ctx exprContext, self *term) (v any, err error) {
rightInt, _ := rightValue.(int64)
v = leftInt - rightInt
}
} else if isList(leftValue) && isList(rightValue) {
leftList, _ := leftValue.([]any)
rightList, _ := rightValue.([]any)
diffList := make([]any, 0, len(leftList)-len(rightList))
for _, item := range leftList {
if slices.Index(rightList, item) < 0 {
diffList = append(diffList, item)
}
}
v = diffList
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
+448 -52
View File
@@ -1,76 +1,472 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// parser.go
package expr
import (
"errors"
"fmt"
)
//-------- parser
type parser struct {
ctx exprContext
ctx ExprContext
}
func NewParser(ctx exprContext) (p *parser) {
func NewParser(ctx ExprContext) (p *parser) {
p = &parser{
ctx: ctx,
}
return p
}
func (self *parser) parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, err error) {
tree = NewAst()
firstToken := true
for tk := scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); tk = scanner.Next() {
if tk.Sym == SymComment {
continue
}
//fmt.Println("Token:", tk)
if firstToken && (tk.Sym == SymMinus || tk.Sym == SymPlus) {
if tk.Sym == SymMinus {
tk.Sym = SymChangeSign
} else {
tk.Sym = SymUnchangeSign
}
}
firstToken = false
if tk.Sym == SymOpenRound {
var subTree *ast
if subTree, err = self.parse(scanner, SymClosedRound); err == nil {
subTree.root.priority = priValue
tree.addTerm(subTree.root)
}
} else if tk.Sym == SymFunction {
name, _ := tk.Value.(string)
funcObj := self.ctx.GetFuncInfo(name)
if funcObj == nil {
err = fmt.Errorf("unknown function %q", name)
break
}
maxArgs := funcObj.MaxArgs()
if maxArgs < 0 {
maxArgs = funcObj.MinArgs() + 10
}
args := make([]*term, 0, maxArgs)
lastSym := SymUnknown
for lastSym != SymClosedRound && lastSym != SymEos {
var subTree *ast
if subTree, err = self.parse(scanner, SymComma, SymClosedRound); err == nil {
args = append(args, subTree.root)
} else {
break
}
lastSym = scanner.Previous().Sym
}
if err == nil {
// TODO Check arguments
t := newFuncTerm(tk, args)
tree.addTerm(t)
func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token) (tree *term, err error) {
// name, _ := tk.Value.(string)
// funcObj := self.ctx.GetFuncInfo(name)
// if funcObj == nil {
// err = fmt.Errorf("unknown function %s()", name)
// return
// }
// maxArgs := funcObj.MaxArgs()
// if maxArgs < 0 {
// maxArgs = funcObj.MinArgs() + 10
// }
// args := make([]*term, 0, maxArgs)
args := make([]*term, 0, 10)
lastSym := SymUnknown
for lastSym != SymClosedRound && lastSym != SymEos {
var subTree *ast
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil {
if subTree.root != nil {
args = append(args, subTree.root)
}
} else {
err = tree.addToken(tk)
break
}
lastSym = scanner.Previous().Sym
}
if err == nil {
// TODO Check arguments
if lastSym != SymClosedRound {
err = errors.New("unterminate arguments list")
} else {
tree = newFuncCallTerm(tk, args)
}
}
return
}
// func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
// // Example: "add = func(x,y) {x+y}
// var body *ast
// args := make([]*term, 0)
// tk := scanner.Next()
// for tk.Sym != SymClosedRound && tk.Sym != SymEos {
// if tk.Sym == SymIdentifier {
// t := newTerm(tk, nil)
// args = append(args, t)
// } else {
// err = tk.Errorf("invalid param %q, variable identifier expected", tk.source)
// break
// }
// tk = scanner.Next()
// }
// if err == nil && tk.Sym != SymClosedRound {
// err = tk.Errorf("unterminate function params list")
// }
// if err == nil {
// tk = scanner.Next()
// if tk.Sym == SymOpenBrace {
// body, err = self.parseGeneral(scanner, true, true, SymClosedBrace)
// }
// }
// if err == nil {
// // TODO Check arguments
// if scanner.Previous().Sym != SymClosedBrace {
// err = scanner.Previous().Errorf("not properly terminated function body")
// } else {
// tk = scanner.makeValueToken(SymExpression, "", body)
// tree = newFuncDefTerm(tk, args)
// }
// }
// return
// }
func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
// Example: "add = func(x,y) {x+y}
var body *ast
args := make([]*term, 0)
lastSym := SymUnknown
itemExpected := false
tk := scanner.Previous()
for lastSym != SymClosedRound && lastSym != SymEos {
var subTree *ast
if subTree, err = self.parseItem(scanner, true, SymComma, SymClosedRound); err == nil {
if subTree.root != nil {
if subTree.root.symbol() == SymIdentifier {
args = append(args, subTree.root)
} else {
err = tk.Errorf("exptected identifier, got %q", subTree.root)
}
} else if itemExpected {
prev := scanner.Previous()
err = prev.Errorf("expected function parameter, got %q", prev)
break
}
} else {
break
}
lastSym = scanner.Previous().Sym
itemExpected = lastSym == SymComma
}
if err == nil && lastSym != SymClosedRound {
err = tk.Errorf("unterminated function parameters list")
}
if err == nil {
tk = scanner.Next()
if tk.Sym == SymOpenBrace {
body, err = self.parseGeneral(scanner, true, true, SymClosedBrace)
}
}
if err == nil {
// TODO Check arguments
if scanner.Previous().Sym != SymClosedBrace {
err = scanner.Previous().Errorf("not properly terminated function body")
} else {
tk = scanner.makeValueToken(SymExpression, "", body)
tree = newFuncDefTerm(tk, args)
}
}
return
}
func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
args := make([]*term, 0)
lastSym := SymUnknown
itemExpected := false
for lastSym != SymClosedSquare && lastSym != SymEos {
var subTree *ast
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
if subTree.root != nil {
args = append(args, subTree.root)
} else if itemExpected {
prev := scanner.Previous()
err = prev.Errorf("expected list item, got %q", prev)
break
}
} else {
break
}
lastSym = scanner.Previous().Sym
itemExpected = lastSym == SymComma
}
if err == nil {
// TODO Check arguments
if lastSym != SymClosedSquare {
err = scanner.Previous().Errorf("unterminate items list")
} else {
subtree = newListTerm(args)
}
}
return
}
func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
var ds *term
tk := scanner.Previous()
args := make([]*term, 0)
lastSym := SymUnknown
dsExpected := true
itemExpected := false
for lastSym != SymClosedRound && lastSym != SymEos {
var subTree *ast
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil {
if subTree.root != nil {
if dsExpected {
if sym := subTree.root.symbol(); sym == SymDict || sym == SymIdentifier {
ds = subTree.root
} else {
err = subTree.root.Errorf("data-source dictionary expected, got %q", subTree.root.source())
}
dsExpected = false
} else {
args = append(args, subTree.root)
}
} else if itemExpected {
prev := scanner.Previous()
err = prev.Errorf("expected iterator argument, got %q", prev)
break
}
} else {
break
}
lastSym = scanner.Previous().Sym
itemExpected = lastSym == SymComma
}
if err == nil {
// TODO Check arguments
if lastSym != SymClosedRound {
err = scanner.Previous().Errorf("unterminate iterator param list")
} else if ds != nil {
subtree = newIteratorTerm(tk, ds, args)
} else {
tk.Errorf("missing data-source param")
}
}
return
}
func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, err error) {
tk := scanner.Next()
if tk.Sym == SymError {
err = tk.Error()
return
}
if tk.Sym == SymClosedBrace || tk.Sym == SymEos {
return
}
if tk.Sym == SymInteger || tk.Sym == SymString {
tkSep := scanner.Next()
if tkSep.Sym != SymColon {
err = tkSep.Errorf("expected \":\", got %q", tkSep)
} else {
key = tk.Value
}
} else {
err = tk.Errorf("expected dictionary key or closed brace, got %q", tk)
}
return
}
func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
args := make(map[any]*term, 0)
lastSym := SymUnknown
itemExpected := false
for lastSym != SymClosedBrace && lastSym != SymEos {
var subTree *ast
var key any
if key, err = self.parseDictKey(scanner, allowVarRef); err != nil {
break
} else if key == nil {
tk := scanner.Previous()
lastSym = tk.Sym
if itemExpected {
err = tk.Errorf("expected dictionary key, got %q", tk)
}
break
}
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedBrace); err == nil {
if subTree.root != nil {
args[key] = subTree.root
} else if key != nil {
prev := scanner.Previous()
err = prev.Errorf("expected dictionary value, got %q", prev)
break
}
} else {
break
}
lastSym = scanner.Previous().Sym
itemExpected = lastSym == SymComma
}
if err == nil {
// TODO Check arguments
if lastSym != SymClosedBrace {
err = scanner.Previous().Errorf("unterminated dictionary")
} else {
subtree = newDictTerm(args)
}
}
return
}
func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaultCase bool) (caseTerm *term, err error) {
var filterList *term
var caseExpr *ast
tk := scanner.Next()
startRow := tk.row
startCol := tk.col
if tk.Sym == SymOpenSquare {
if defaultCase {
err = tk.Errorf("case list in default clause")
return
}
if filterList, err = self.parseList(scanner, allowVarRef); err != nil {
return
}
tk = scanner.Next()
startRow = tk.row
startCol = tk.col
} else if !defaultCase {
filterList = newListTerm(make([]*term, 0))
}
if tk.Sym == SymOpenBrace {
if caseExpr, err = self.parseGeneral(scanner, true, allowVarRef, SymClosedBrace); err != nil {
return
}
} else {
err = tk.Errorf("selector-case expected, got %q", tk.source)
}
if err == nil {
caseTerm = newSelectorCaseTerm(startRow, startCol, filterList, caseExpr)
}
return
}
func addSelectorCase(selectorTerm, caseTerm *term) {
if len(selectorTerm.children) < 2 {
caseListTerm := newListTermA(caseTerm)
selectorTerm.children = append(selectorTerm.children, caseListTerm)
} else {
caseListTerm := selectorTerm.children[1]
caseList, _ := caseListTerm.value().([]*term)
caseList = append(caseList, caseTerm)
caseListTerm.tk.Value = caseList
}
caseTerm.parent = selectorTerm
}
func (self *parser) parseSelector(scanner *scanner, tree *ast, allowVarRef bool) (selectorTerm *term, err error) {
var caseTerm *term
tk := scanner.makeToken(SymSelector, '?')
if selectorTerm, err = tree.addToken2(tk); err != nil {
return
}
if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, false); err == nil {
addSelectorCase(selectorTerm, caseTerm)
}
return
}
func (self *parser) parseItem(scanner *scanner, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
return self.parseGeneral(scanner, false, allowVarRef, termSymbols...)
}
func (self *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, err error) {
return self.parseGeneral(scanner, true, false, termSymbols...)
}
func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
var selectorTerm *term = nil
var currentTerm *term = nil
var tk *Token
tree = NewAst()
firstToken := true
lastSym := SymUnknown
for tk = scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); tk = scanner.Next() {
if tk.Sym == SymComment {
continue
}
if tk.Sym == SymSemiColon {
if allowForest {
tree.ToForest()
firstToken = true
continue
} else {
err = tk.Errorf(`unexpected token %q, expected ",", "]", or ")"`, tk.source)
break
}
}
//fmt.Println("Token:", tk)
if firstToken {
if tk.Sym == SymMinus {
tk.Sym = SymChangeSign
} else if tk.Sym == SymPlus {
tk.Sym = SymUnchangeSign
}
firstToken = false
}
switch tk.Sym {
case SymOpenRound:
var subTree *ast
if subTree, err = self.parseGeneral(scanner, false, allowVarRef, SymClosedRound); err == nil {
subTree.root.priority = priValue
err = tree.addTerm(subTree.root)
currentTerm = subTree.root
}
case SymFuncCall:
var funcCallTerm *term
if funcCallTerm, err = self.parseFuncCall(scanner, allowVarRef, tk); err == nil {
err = tree.addTerm(funcCallTerm)
currentTerm = funcCallTerm
}
case SymOpenSquare:
var listTerm *term
if listTerm, err = self.parseList(scanner, allowVarRef); err == nil {
err = tree.addTerm(listTerm)
currentTerm = listTerm
}
case SymOpenBrace:
var mapTerm *term
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
err = tree.addTerm(mapTerm)
currentTerm = mapTerm
}
case SymEqual:
if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
currentTerm, err = tree.addToken2(tk)
}
case SymFuncDef:
var funcDefTerm *term
if funcDefTerm, err = self.parseFuncDef(scanner); err == nil {
err = tree.addTerm(funcDefTerm)
currentTerm = funcDefTerm
}
case SymDollarRound:
var iterDefTerm *term
if iterDefTerm, err = self.parseIterDef(scanner, allowVarRef); err == nil {
err = tree.addTerm(iterDefTerm)
currentTerm = iterDefTerm
}
case SymIdentifier:
if tk.source[0] == '@' && !allowVarRef {
err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source)
} else {
currentTerm, err = tree.addToken2(tk)
}
case SymQuestion:
if selectorTerm, err = self.parseSelector(scanner, tree, allowVarRef); err == nil {
currentTerm = selectorTerm
}
case SymColon, SymDoubleColon:
var caseTerm *term
if selectorTerm == nil {
err = tk.Errorf("selector-case outside of a selector context")
} else if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
addSelectorCase(selectorTerm, caseTerm)
currentTerm = caseTerm
if tk.Sym == SymDoubleColon {
selectorTerm = nil
}
}
default:
currentTerm, err = tree.addToken2(tk)
}
if currentTerm != nil && currentTerm.tk.Sym != SymSelector && currentTerm.parent != nil && currentTerm.parent.tk.Sym != SymSelector {
selectorTerm = nil
}
lastSym = tk.Sym
}
if err == nil {
err = tk.Error()
}
return
}
func checkPrevSymbol(lastSym, wantedSym Symbol, tk *Token) (err error) {
if lastSym != wantedSym {
err = fmt.Errorf(`assign operator (%q) must be preceded by a variable`, tk.source)
}
return
}
+285 -174
View File
@@ -1,96 +1,17 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// parser_test.go
package expr
import (
"errors"
"fmt"
"reflect"
"strings"
"testing"
)
type testContext struct {
store map[string]any
}
func newTestContext() *testContext {
return &testContext{
store: make(map[string]any),
}
}
func (ctx *testContext) GetValue(varName string) (v any, exists bool) {
v, exists = ctx.store[varName]
return
}
func (ctx *testContext) SetValue(varName string, value any) {
ctx.store[varName] = value
}
func (ctx *testContext) GetFuncInfo(name string) (f exprFunc) {
if name == "ADD" {
f = &testAddFunc{}
}
return
}
func (ctx *testContext) Call(name string, args []any) (result any, err error) {
if name == "ADD" {
funcObj := &testAddFunc{}
result, err = funcObj.Invoke(ctx, args)
} else {
err = fmt.Errorf("unknown function %q", name)
}
return
}
type testAddFunc struct {
}
func (self *testAddFunc) Name() string {
return "ADD"
}
func (self *testAddFunc) MinArgs() int {
return 1
}
func (self *testAddFunc) MaxArgs() int {
return -1
}
func (self *testAddFunc) Invoke(ctx exprContext, args []any) (result any, err error) {
var sumAsFloat = false
var floatSum float64 = 0.0
var intSum int64 = 0
for i, v := range args {
if !isNumber(v) {
err = fmt.Errorf("add(): param nr %d has wrong type %T, number expected", i+1, v)
break
}
if !sumAsFloat && isFloat(v) {
sumAsFloat = true
floatSum = float64(intSum)
}
if sumAsFloat {
floatSum += numAsFloat(v)
} else {
iv, _ := v.(int64)
intSum += iv
}
}
if err == nil {
if sumAsFloat {
result = floatSum
} else {
result = intSum
}
}
return
}
func TestParser(t *testing.T) {
type inputType struct {
source string
@@ -98,118 +19,308 @@ func TestParser(t *testing.T) {
wantErr error
}
ctx := newTestContext()
ctx.SetValue("var1", int64(123))
ctx.SetValue("var2", "abc")
inputs := []inputType{
{`123`, int64(123), nil},
{`1.`, float64(1.0), nil},
{`1.E-2`, float64(0.01), nil},
{`1E2`, float64(100), nil},
/* 1 */ {`1+/*5*/2`, int64(3), nil},
/* 2 */ {`3 == 4`, false, nil},
/* 3 */ {`3 != 4`, true, nil},
/* 4 */ {`4 == (3-1)*(10/5)`, true, nil},
/* 5 */ {`3 <= 4`, true, nil},
/* 6 */ {`3 < 4`, true, nil},
/* 7 */ {`4 < 3`, false, nil},
/* 8 */ {`1+5 < 4`, false, nil},
/* 9 */ {`3 > 4`, false, nil},
/* 10 */ {`4 >= 4`, true, nil},
/* 11 */ {`4 > 3`, true, nil},
/* 12 */ {`1+5 > 4`, true, nil},
/* 13 */ {`true`, true, nil},
/* 14 */ {`not true`, false, nil},
/* 15 */ {`true and false`, false, nil},
/* 16 */ {`true or false`, true, nil},
/* 17 */ {`"uno" + "due"`, `unodue`, nil},
/* 18 */ {`"uno" + 2`, `uno2`, nil},
/* 19 */ {`"uno" + (2+1)`, `uno3`, nil},
/* 20 */ {`"uno" * (2+1)`, `unounouno`, nil},
/* 21 */ {"1", int64(1), nil},
/* 22 */ {"1.5", float64(1.5), nil},
/* 23 */ {"1.5*2", float64(3.0), nil},
/* 24 */ {"+1", int64(1), nil},
/* 25 */ {"-1", int64(-1), nil},
/* 26 */ {"435 + 105 * 2 - 1", int64(644), nil},
/* 27 */ {"200 / 2 - 1", int64(99), nil},
/* 28 */ {"(1+1)", int64(2), nil},
/* 29 */ {"-(1+1)", int64(-2), nil},
/* 30 */ {"-(-2+1)", int64(1), nil},
/* 31 */ {"(1+1)*5", int64(10), nil},
/* 32 */ {"200 / (1+1) - 1", int64(99), nil},
/* 33 */ {`add(1,2,3)`, int64(6), nil},
/* 34 */ {`mul(1,2,3)`, nil, errors.New(`unknown function "MUL"`)},
/* 35 */ {`add(1+4,3+2,5*(3-2))`, int64(15), nil},
/* 36 */ {`add(add(1+4),3+2,5*(3-2))`, int64(15), nil},
/* 37 */ {`add(add(1+4),/*3+2,*/5*(3-2))`, int64(10), nil},
/* 38 */ {`1+(1+(1+(1)+1)+1)+1`, int64(7), nil},
/* 39 */ {`(((1)))`, int64(1), nil},
/* 40 */ {`"uno_" + var2`, `uno_abc`, nil},
/* 41 */ {`0 || 0.0 && "hello"`, false, nil},
/* 42 */ {`"s" + true`, nil, errors.New(`left operand 's' [string] is not compatible with right operand 'true' [bool] with respect to operator "+"`)},
/* 43 */ {`+false`, nil, errors.New(`prefix/postfix operator "+" do not support operand 'false' [bool]`)},
/* 44 */ {`false // very simple expression`, false, nil},
/* 45 */ {`1 + // Missing right operator`, nil, errors.New(`infix operator "+" requires two operands, got 1`)},
/* 46 */ {"", nil, errors.New(`empty expression`)},
/* 47 */ {"4!", int64(24), nil},
/* 48 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)},
/* 49 */ {"-4!", int64(-24), nil},
/* 50 */ {"1.5 < 7", true, nil},
/* 51 */ {"1.5 > 7", false, nil},
/* 52 */ {"1.5 <= 7", true, nil},
/* 53 */ {"1.5 >= 7", false, nil},
/* 54 */ {"1.5 != 7", true, nil},
/* 55 */ {"1.5 == 7", false, nil},
/* 56 */ {`"1.5" < "7"`, true, nil},
/* 57 */ {`"1.5" > "7"`, false, nil},
/* 58 */ {`"1.5" == "7"`, false, nil},
/* 59 */ {`"1.5" != "7"`, true, nil},
/* 60 */ {"1.5 < ", nil, errors.New(`infix operator "<" requires two operands, got 1`)},
/* 61 */ {"1.5 > ", nil, errors.New(`infix operator ">" requires two operands, got 1`)},
/* 62 */ {"1.5 <= ", nil, errors.New(`infix operator "<=" requires two operands, got 1`)},
/* 63 */ {"1.5 >= ", nil, errors.New(`infix operator ">=" requires two operands, got 1`)},
/* 64 */ {"1.5 != ", nil, errors.New(`infix operator "!=" requires two operands, got 1`)},
/* 65 */ {"1.5 == ", nil, errors.New(`infix operator "==" requires two operands, got 1`)},
/* 66 */ {`"1.5" < `, nil, errors.New(`infix operator "<" requires two operands, got 1`)},
/* 67 */ {`"1.5" > `, nil, errors.New(`infix operator ">" requires two operands, got 1`)},
/* 68 */ {`"1.5" == `, nil, errors.New(`infix operator "==" requires two operands, got 1`)},
/* 69 */ {`"1.5" != `, nil, errors.New(`infix operator "!=" requires two operands, got 1`)},
/* 70 */ {"+1.5", float64(1.5), nil},
/* 71 */ {"+", nil, errors.New(`prefix operator "+" requires one operand`)},
/* 72 */ {"4 / 0", nil, errors.New(`division by zero`)},
/* 73 */ {"4.0 / 0", nil, errors.New(`division by zero`)},
/* 74 */ {"4.0 / \n2", float64(2.0), nil},
/* 1 */ {`1+/*5*/2`, int64(3), nil},
/* 2 */ {`3 == 4`, false, nil},
/* 3 */ {`3 != 4`, true, nil},
/* 4 */ {`4 == (3-1)*(10/5)`, true, nil},
/* 5 */ {`3 <= 4`, true, nil},
/* 6 */ {`3 < 4`, true, nil},
/* 7 */ {`4 < 3`, false, nil},
/* 8 */ {`1+5 < 4`, false, nil},
/* 9 */ {`3 > 4`, false, nil},
/* 10 */ {`4 >= 4`, true, nil},
/* 11 */ {`4 > 3`, true, nil},
/* 12 */ {`1+5 > 4`, true, nil},
/* 13 */ {`true`, true, nil},
/* 14 */ {`not true`, false, nil},
/* 15 */ {`true and false`, false, nil},
/* 16 */ {`true or false`, true, nil},
/* 17 */ {`"uno" + "due"`, `unodue`, nil},
/* 18 */ {`"uno" + 2`, `uno2`, nil},
/* 19 */ {`"uno" + (2+1)`, `uno3`, nil},
/* 20 */ {`"uno" * (2+1)`, `unounouno`, nil},
/* 21 */ {"1", int64(1), nil},
/* 22 */ {"1.5", float64(1.5), nil},
/* 23 */ {"1.5*2", float64(3.0), nil},
/* 24 */ {"+1", int64(1), nil},
/* 25 */ {"-1", int64(-1), nil},
/* 26 */ {"435 + 105 * 2 - 1", int64(644), nil},
/* 27 */ {"200 / 2 - 1", int64(99), nil},
/* 28 */ {"(1+1)", int64(2), nil},
/* 29 */ {"-(1+1)", int64(-2), nil},
/* 30 */ {"-(-2+1)", int64(1), nil},
/* 31 */ {"(1+1)*5", int64(10), nil},
/* 32 */ {"200 / (1+1) - 1", int64(99), nil},
/* 33 */ {`add(1,2,3)`, int64(6), nil},
/* 34 */ {`mulX(1,2,3)`, nil, errors.New(`unknown function mulX()`)},
/* 35 */ {`add(1+4,3+2,5*(3-2))`, int64(15), nil},
/* 36 */ {`add(add(1+4),3+2,5*(3-2))`, int64(15), nil},
/* 37 */ {`add(add(1,4),/*3+2,*/5*(3-2))`, int64(10), nil},
/* 38 */ {`1+(1+(1+(1)+1)+1)+1`, int64(7), nil},
/* 39 */ {`(((1)))`, int64(1), nil},
/* 40 */ {`"uno_" + var2`, `uno_abc`, nil},
/* 41 */ {`0 || 0.0 && "hello"`, false, nil},
/* 42 */ {`"s" + true`, nil, errors.New(`[1:6] left operand 's' [string] and right operand 'true' [bool] are not compatible with operator "+"`)},
/* 43 */ {`+false`, nil, errors.New(`[1:2] prefix/postfix operator "+" do not support operand 'false' [bool]`)},
/* 44 */ {`false // very simple expression`, false, nil},
/* 45 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two non-nil operands, got 1`)},
/* 46 */ {"", nil, nil},
/* 47 */ {"4!", int64(24), nil},
/* 48 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)},
/* 49 */ {"-4!", int64(-24), nil},
/* 50 */ {"1.5 < 7", true, nil},
/* 51 */ {"1.5 > 7", false, nil},
/* 52 */ {"1.5 <= 7", true, nil},
/* 53 */ {"1.5 >= 7", false, nil},
/* 54 */ {"1.5 != 7", true, nil},
/* 55 */ {"1.5 == 7", false, nil},
/* 56 */ {`"1.5" < "7"`, true, nil},
/* 57 */ {`"1.5" > "7"`, false, nil},
/* 58 */ {`"1.5" == "7"`, false, nil},
/* 59 */ {`"1.5" != "7"`, true, nil},
/* 60 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two non-nil operands, got 1`)},
/* 61 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two non-nil operands, got 1`)},
/* 62 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two non-nil operands, got 1`)},
/* 63 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two non-nil operands, got 1`)},
/* 64 */ {"1.5 != ", nil, errors.New(`[1:6] infix operator "!=" requires two non-nil operands, got 1`)},
/* 65 */ {"1.5 == ", nil, errors.New(`[1:6] infix operator "==" requires two non-nil operands, got 1`)},
/* 66 */ {`"1.5" < `, nil, errors.New(`[1:8] infix operator "<" requires two non-nil operands, got 1`)},
/* 67 */ {`"1.5" > `, nil, errors.New(`[1:8] infix operator ">" requires two non-nil operands, got 1`)},
/* 68 */ {`"1.5" == `, nil, errors.New(`[1:8] infix operator "==" requires two non-nil operands, got 1`)},
/* 69 */ {`"1.5" != `, nil, errors.New(`[1:8] infix operator "!=" requires two non-nil operands, got 1`)},
/* 70 */ {"+1.5", float64(1.5), nil},
/* 71 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one not nil operand`)},
/* 72 */ {"4 / 0", nil, errors.New(`division by zero`)},
/* 73 */ {"4.0 / 0", nil, errors.New(`division by zero`)},
/* 74 */ {"4.0 / \n2", float64(2.0), nil},
/* 75 */ {`123`, int64(123), nil},
/* 76 */ {`1.`, float64(1.0), nil},
/* 77 */ {`1.E-2`, float64(0.01), nil},
/* 78 */ {`1E2`, float64(100), nil},
/* 79 */ {`1 / 2`, int64(0), nil},
/* 80 */ {`1.0 / 2`, float64(0.5), nil},
/* 81 */ {`1 ./ 2`, float64(0.5), nil},
/* 82 */ {`5 % 2`, int64(1), nil},
/* 83 */ {`5 % (-2)`, int64(1), nil},
/* 84 */ {`-5 % 2`, int64(-1), nil},
/* 85 */ {`5 % 2.0`, nil, errors.New(`[1:4] left operand '5' [int64] and right operand '2' [float64] are not compatible with operator "%"`)},
/* 86 */ {`"a" < "b" AND NOT (2 < 1)`, true, nil},
/* 87 */ {`"a" < "b" AND NOT (2 == 1)`, true, nil},
/* 88 */ {`"a" < "b" AND ~ 2 == 1`, true, nil},
/* 89 */ {`~ 2 > 1`, false, nil},
/* 90 */ {`~ true && true`, false, nil},
/* 91 */ {`~ false || true`, true, nil},
/* 92 */ {`false but true`, true, nil},
/* 93 */ {`2+3 but 5*2`, int64(10), nil},
/* 94 */ {`add(1,2) but var2`, "abc", nil},
/* 95 */ {`x=2`, int64(2), nil},
/* 96 */ {`x=2 but x*10`, int64(20), nil},
/* 97 */ {`false and true`, false, nil},
/* 98 */ {`false and (x==2)`, false, nil},
/* 99 */ {`false and (x=2 but x==2) or x==2`, nil, errors.New(`undefined variable or function "x"`)},
/* 100 */ {`false or true`, true, nil},
/* 101 */ {`false or (x==2)`, nil, errors.New(`undefined variable or function "x"`)},
/* 102 */ {`a=5; a`, int64(5), nil},
/* 103 */ {`a=5; b=2; add(a, b*3)`, int64(11), nil},
/* 104 */ {`2=5`, nil, errors.New(`assign operator ("=") must be preceded by a variable`)},
/* 105 */ {`2+a=5`, nil, errors.New(`[1:3] left operand of "=" must be a variable`)},
/* 106 */ {`2+(a=5)`, int64(7), nil},
/* 107 */ {`two=func(){2}; two()`, int64(2), nil},
/* 108 */ {`double=func(x) {2*x}; (double(3))`, int64(6), nil},
/* 109 */ {`double=func(x){2*x}; double(3)`, int64(6), nil},
/* 110 */ {`double=func(x){2*x}; a=5; double(3+a) + 1`, int64(17), nil},
/* 111 */ {`double=func(x){2*x}; a=5; two=func() {2}; (double(3+a) + 1) * two()`, int64(34), nil},
/* 112 */ {`import("./test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 113 */ {`import("test-funcs.expr"); (double(3+a) + 1) * two()`, int64(34), nil},
/* 114 */ {`x ?? "default"`, "default", nil},
/* 115 */ {`x="hello"; x ?? "default"`, "hello", nil},
/* 116 */ {`y=x ?? func(){4}; y()`, int64(4), nil},
/* 117 */ {`x ?= "default"; x`, "default", nil},
/* 118 */ {`x="hello"; x ?= "default"; x`, "hello", nil},
/* 119 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
/* 120 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
/* 121 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
/* 122 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable or function "x"`)},
/* 123 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 124 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
/* 125 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
/* 126 */ {`include("./test-funcs.expr"); six()`, int64(6), nil},
/* 127 */ {`import("./sample-export-all.expr"); six()`, int64(6), nil},
/* 128 */ {`1 ? {"a"} : {"b"}`, "b", nil},
/* 129 */ {`10 ? {"a"} : {"b"} :: {"c"}`, "c", nil},
/* 130 */ {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", nil},
/* 131 */ {`10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"}`, nil, errors.New(`[1:34] case list in default clause`)},
/* 132 */ {`10 ? {"a"} :[10] {x="b" but x} :: {"c"}`, "b", nil},
/* 133 */ {`10 ? {"a"} :[10] {x="b"; x} :: {"c"}`, "b", nil},
/* 134 */ {`10 ? {"a"} : {"b"}`, nil, errors.New(`[1:3] no case catches the value (10) of the selection expression`)},
/* 135 */ {`10 ? {"a"} :: {"b"} : {"c"}`, nil, errors.New(`[1:22] selector-case outside of a selector context`)},
/* 136 */ {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
/* 137 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
/* 138 */ {`nil`, nil, nil},
/* 139 */ {`null`, nil, errors.New(`undefined variable or function "null"`)},
/* 140 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)},
/* 141 */ {`{"key":}`, nil, errors.New(`[1:9] expected dictionary value, got "}"`)},
/* 142 */ {`{}`, map[any]any{}, nil},
/* 144 */ //{`3^2`, int64(9), nil},
}
check_env_expr_path := 113
succeeded := 0
failed := 0
// inputs1 := []inputType{
// /* 140 */ {`ds={}; $(ds)`, nil, nil},
// }
for i, input := range inputs {
var expr Expr
var gotResult any
var gotErr error
ctx := NewSimpleFuncStore()
ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc")
ImportMathFuncs(ctx)
ImportImportFuncs(ctx)
parser := NewParser(ctx)
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good := true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
eq := reflect.DeepEqual(gotResult, input.wantResult)
if !eq /*gotResult != input.wantResult*/ {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
good = false
}
}
if good {
succeeded++
} else {
failed++
if i+1 == check_env_expr_path {
t.Logf(`NOTICE: Test nr %d requires EXPR_PATH="."`, check_env_expr_path)
}
}
}
t.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed))
}
func TestListParser(t *testing.T) {
type inputType struct {
source string
wantResult any
wantErr error
}
parser := NewParser(ctx)
inputs := []inputType{
/* 1 */ {`[]`, []any{}, nil},
/* 2 */ {`[1,2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
/* 3 */ {`[1,2,"hello"]`, []any{int64(1), int64(2), "hello"}, nil},
/* 4 */ {`[1+2, not true, "hello"]`, []any{int64(3), false, "hello"}, nil},
/* 5 */ {`[1,2]+[3]`, []any{int64(1), int64(2), int64(3)}, nil},
/* 6 */ {`[1,4,3,2]-[3]`, []any{int64(1), int64(4), int64(2)}, nil},
/* 7 */ {`add([1,4,3,2])`, int64(10), nil},
/* 8 */ {`add([1,[2,2],3,2])`, int64(10), nil},
/* 9 */ {`mul([1,4,3.0,2])`, float64(24.0), nil},
/* 10 */ {`add([1,"hello"])`, nil, errors.New(`add(): param nr 2 has wrong type string, number expected`)},
/* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
// /* 8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, nil},
// /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil},
}
succeeded := 0
failed := 0
// inputs1 := []inputType{
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
// }
for i, input := range inputs {
var expr *ast
var gotResult any
var gotErr error
if input.wantErr == nil {
t.Log(fmt.Sprintf("[+]Test nr %2d -- %q", i+1, input.source))
} else {
t.Log(fmt.Sprintf("[-]Test nr %2d -- %q", i+1, input.source))
}
ctx := NewSimpleFuncStore()
ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc")
ImportMathFuncs(ctx)
parser := NewParser(ctx)
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
if expr, gotErr = parser.parse(scanner); gotErr == nil {
gotResult, gotErr = expr.eval(ctx)
good := true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
if gotResult != input.wantResult {
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.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed))
}
func logTest(t *testing.T, n int, source string, wantResult any, wantErr error) {
if wantErr == nil {
t.Log(fmt.Sprintf("[+]Test nr %3d -- %q --> %v", n, source, wantResult))
} else {
t.Log(fmt.Sprintf("[-]Test nr %3d -- %q --> %v", n, source, wantErr))
}
}
+17
View File
@@ -0,0 +1,17 @@
/*
sample-export-all.expr: example source file
*/
@@; // Enable export all mode
// double(x): returns 2*x
double=func(x){2*x};
// Define variable 'a' wth value 5
a=5;
// two(): returns 2
two=func() {2};
// six(): returns 6
six=func() {6};
+158 -114
View File
@@ -1,9 +1,13 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// expr project scanner.go
package expr
import (
"bufio"
"errors"
"fmt"
"io"
"strconv"
"strings"
@@ -35,6 +39,7 @@ func DefaultTranslations() map[Symbol]Symbol {
SymKwAnd: SymAnd,
SymDoubleVertBar: SymOr,
SymKwOr: SymOr,
SymTilde: SymNot,
SymKwNot: SymNot,
SymLessGreater: SymNotEqual,
}
@@ -81,13 +86,14 @@ func (self *scanner) Next() (tk *Token) {
}
func (self *scanner) fetchNextToken() (tk *Token) {
var ch byte
if err := self.skipBlanks(); err != nil {
return self.makeErrorToken(err)
}
escape := false
for {
ch, _ := self.readChar()
ch, _ = self.readChar()
switch ch {
case '+':
if next, _ := self.peek(); next == '+' {
@@ -138,13 +144,22 @@ func (self *scanner) fetchNextToken() (tk *Token) {
}
case ',':
tk = self.makeToken(SymComma, ch)
case '^':
tk = self.makeToken(SymCaret, ch)
case ':':
tk = self.makeToken(SymColon, ch)
if next, _ := self.peek(); next == ':' {
tk = self.moveOn(SymDoubleColon, ch, next)
} else {
tk = self.makeToken(SymColon, ch)
}
case ';':
tk = self.makeToken(SymSemiColon, ch)
case '.':
if next, _ := self.peek(); next >= '0' && next <= '9' {
tk = self.parseNumber(ch)
//if next, _ := self.peek(); next >= '0' && next <= '9' {
// tk = self.parseNumber(ch)
//} else if next == '/' {
if next, _ := self.peek(); next == '/' {
tk = self.moveOn(SymDotSlash, ch, next)
} else {
tk = self.makeToken(SymDot, ch)
}
@@ -166,7 +181,13 @@ func (self *scanner) fetchNextToken() (tk *Token) {
tk = self.makeToken(SymExclamation, ch)
}
case '?':
tk = self.makeToken(SymQuestion, ch)
if next, _ := self.peek(); next == '?' {
tk = self.moveOn(SymDoubleQuestion, ch, next)
} else if next, _ := self.peek(); next == '=' {
tk = self.moveOn(SymQuestionEqual, ch, next)
} else {
tk = self.makeToken(SymQuestion, ch)
}
case '&':
if next, _ := self.peek(); next == '&' {
tk = self.moveOn(SymDoubleAmpersand, ch, next)
@@ -178,7 +199,19 @@ func (self *scanner) fetchNextToken() (tk *Token) {
case '#':
tk = self.makeToken(SymHash, ch)
case '@':
tk = self.makeToken(SymAt, ch)
if next, _ := self.peek(); (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z') {
self.readChar()
if tk = self.fetchIdentifier(next); tk.Sym == SymIdentifier {
//tk.Sym = SymIdRef
tk.source = "@" + tk.source
} else {
tk = self.makeErrorToken(fmt.Errorf("invalid variable reference %q", tk.source))
}
} else if next == '@' {
tk = self.moveOn(SymDoubleAt, ch, next)
} else {
tk = self.makeToken(SymAt, ch)
}
case '_':
tk = self.makeToken(SymUndescore, ch)
case '=':
@@ -190,6 +223,8 @@ func (self *scanner) fetchNextToken() (tk *Token) {
case '<':
if next, _ := self.peek(); next == '=' {
tk = self.moveOn(SymLessOrEqual, ch, next)
} else if next == '<' {
tk = self.moveOn(SymAppend, ch, next)
} else if next == '>' {
tk = self.moveOn(SymLessGreater, ch, next)
} else {
@@ -198,13 +233,26 @@ func (self *scanner) fetchNextToken() (tk *Token) {
case '>':
if next, _ := self.peek(); next == '=' {
tk = self.moveOn(SymGreaterOrEqual, ch, next)
} else if next == '>' {
tk = self.moveOn(SymInsert, ch, next)
} else {
tk = self.makeToken(SymGreater, ch)
}
case '$':
tk = self.makeToken(SymDollar, ch)
if next, _ := self.peek(); next == '(' {
tk = self.moveOn(SymDollarRound, ch, next)
tk.source += ")"
} else if next == '$' {
tk = self.moveOn(SymDoubleDollar, ch, next)
} else {
tk = self.makeToken(SymDollar, ch)
}
case '(':
tk = self.makeToken(SymOpenRound, ch)
if next, _ := self.peek(); next == ')' {
tk = self.moveOn(SymOpenClosedRound, ch, next)
} else {
tk = self.makeToken(SymOpenRound, ch)
}
case ')':
tk = self.makeToken(SymClosedRound, ch)
case '[':
@@ -215,14 +263,20 @@ func (self *scanner) fetchNextToken() (tk *Token) {
tk = self.makeToken(SymOpenBrace, ch)
case '}':
tk = self.makeToken(SymClosedBrace, ch)
case '~':
tk = self.makeToken(SymTilde, ch)
case 0:
if escape {
tk = self.makeErrorToken(errors.New("incomplete escape sequence"))
}
escape = false
default:
if ch == '_' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') {
tk = self.fetchIdentifier(ch)
if /*ch == '_' ||*/ (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') {
if tk = self.fetchIdentifier(ch); tk.Sym == SymKwFunc {
if next, _ := self.peek(); next == '(' {
tk = self.moveOn(SymFuncDef, ch, next)
}
}
} else if ch >= '0' && ch <= '9' {
tk = self.parseNumber(ch)
}
@@ -231,6 +285,9 @@ func (self *scanner) fetchNextToken() (tk *Token) {
break
}
}
if tk == nil {
tk = NewErrorToken(self.row, self.column, fmt.Errorf("unknown symbol '%c'", ch))
}
return
}
@@ -241,62 +298,116 @@ func (self *scanner) sync(err error) error {
return err
}
func isBinaryDigit(ch byte) bool {
return ch == '0' || ch == '1'
}
func isOctalDigit(ch byte) bool {
return ch >= '0' && ch <= '7'
}
func isDecimalDigit(ch byte) bool {
return ch >= '0' && ch <= '9'
}
func isHexDigit(ch byte) bool {
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
}
func (self *scanner) initBase(sb *strings.Builder, currentFirstCh byte) (firstCh byte, numBase int, digitFunc func(byte) bool, err error) {
var ch byte
var digitType string
firstCh = currentFirstCh
digitFunc = isDecimalDigit
numBase = 10
if ch, err = self.peek(); err == nil {
if ch == 'b' || ch == 'B' {
numBase = 2
digitType = "binary"
self.readChar()
digitFunc = isBinaryDigit
firstCh, err = self.readChar()
} else if ch == 'o' || ch == 'O' {
numBase = 8
digitType = "octal"
self.readChar()
digitFunc = isOctalDigit
firstCh, err = self.readChar()
} else if ch == 'x' || ch == 'X' {
numBase = 16
digitType = "hex"
self.readChar()
digitFunc = isHexDigit
firstCh, err = self.readChar()
}
if err == nil && !digitFunc(firstCh) {
if len(digitType) == 0 {
digitType = "decimal"
}
err = fmt.Errorf("expected %s digit, got '%c'", digitType, firstCh)
}
} else if err == io.EOF {
err = nil
}
return
}
func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
var err error
var ch byte
var sym Symbol = SymInteger
var value any
var sb strings.Builder
var isDigit func(byte) bool = isDecimalDigit
var numBase = 10
for ch = firstCh; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
if firstCh == '0' {
firstCh, numBase, isDigit, err = self.initBase(&sb, firstCh)
}
for ch = firstCh; err == nil && isDigit(ch); ch, err = self.readChar() {
sb.WriteByte(ch)
}
if ch == '.' {
sym = SymFloat
sb.WriteByte(ch)
ch, err = self.readChar()
if ch >= '0' && ch <= '9' {
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
sb.WriteByte(ch)
}
}
}
if ch == 'e' || ch == 'E' {
sym = SymFloat
sb.WriteByte(ch)
if ch, err = self.readChar(); err == nil {
if ch == '+' || ch == '-' {
sb.WriteByte(ch)
ch, err = self.readChar()
}
if numBase == 10 {
if err == nil && ch == '.' {
sym = SymFloat
sb.WriteByte(ch)
ch, err = self.readChar()
if ch >= '0' && ch <= '9' {
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
sb.WriteByte(ch)
}
//err = self.sync(err)
} else {
err = errors.New("expected integer exponent")
}
}
// } else {
// err = self.sync(err)
if err == nil && (ch == 'e' || ch == 'E') {
sym = SymFloat
sb.WriteByte(ch)
if ch, err = self.readChar(); err == nil {
if ch == '+' || ch == '-' {
sb.WriteByte(ch)
ch, err = self.readChar()
}
if ch >= '0' && ch <= '9' {
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
sb.WriteByte(ch)
}
} else {
err = errors.New("expected integer exponent")
}
}
}
}
if err != nil && err != io.EOF {
tk = self.makeErrorToken(err)
} else {
var value any
err = self.sync(err)
txt := sb.String()
if sym == SymFloat {
value, err = strconv.ParseFloat(txt, 64)
} else if strings.HasPrefix(txt, "0x") {
value, err = strconv.ParseInt(txt, 16, 64)
} else if strings.HasPrefix(txt, "0o") {
value, err = strconv.ParseInt(txt, 8, 64)
} else if strings.HasPrefix(txt, "0b") {
value, err = strconv.ParseInt(txt, 2, 64)
} else {
value, err = strconv.ParseInt(txt, 10, 64)
value, err = strconv.ParseInt(txt, numBase, 64)
}
if err == nil {
tk = self.makeValueToken(sym, txt, value)
@@ -329,7 +440,7 @@ func (self *scanner) fetchIdentifier(firstCh byte) (tk *Token) {
tk = self.makeValueToken(SymBool, txt, false)
} else if ch, _ := self.peek(); ch == '(' {
self.readChar()
tk = self.makeValueToken(SymFunction, txt+"(", uptxt)
tk = self.makeValueToken(SymFuncCall, txt+"(", txt)
} else {
tk = self.makeValueToken(SymIdentifier, txt, txt)
}
@@ -359,73 +470,6 @@ func (self *scanner) fetchOnLineComment() *Token {
return self.fetchUntil(SymComment, true, '\n')
}
// func (self *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk *Token) {
// var err error
// var ch byte
// var sb strings.Builder
// var value string
// end := string(endings)
// endReached := false
// for ch, err = self.readChar(); err == nil && !endReached; {
// sb.WriteByte(ch)
// if sb.Len() >= len(end) && strings.HasSuffix(sb.String(), end) {
// value = sb.String()[0 : sb.Len()-len(end)]
// endReached = true
// } else {
// ch, err = self.readChar()
// }
// }
// if !endReached && allowEos {
// value = sb.String()
// endReached = true
// }
// if endReached {
// tk = self.makeValueToken(sym, "", value)
// } else {
// tk = self.makeErrorToken(err)
// }
// return
// }
// func (self *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk *Token) {
// var err error
// var ch byte
// var sb strings.Builder
// var value string
// end := make([]byte, len(endings))
// length := 0
// endReached := false
// for ch, err = self.readChar(); err == nil && !endReached; {
// sb.WriteByte(ch)
// if length == len(endings) {
// for i := 0; i < length-1; i++ {
// end[i] = end[i+1]
// }
// length--
// }
// end[length] = ch
// length++
// if bytes.Equal(endings, end) {
// value = sb.String()[0 : sb.Len()-len(end)]
// endReached = true
// } else {
// ch, err = self.readChar()
// }
// }
// if !endReached && allowEos {
// value = sb.String()
// endReached = true
// }
// if endReached {
// tk = self.makeValueToken(sym, "", value)
// } else {
// tk = self.makeErrorToken(err)
// }
// return
// }
func (self *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk *Token) {
var err error
var ch byte
@@ -525,7 +569,7 @@ func (self *scanner) translate(sym Symbol) Symbol {
}
func (self *scanner) moveOn(sym Symbol, chars ...byte) (tk *Token) {
tk = NewToken(self.translate(sym), string(chars))
tk = NewToken(self.row, self.column, self.translate(sym), string(chars))
for i := 1; i < len(chars); i++ {
self.readChar()
}
@@ -533,17 +577,17 @@ func (self *scanner) moveOn(sym Symbol, chars ...byte) (tk *Token) {
}
func (self *scanner) makeToken(sym Symbol, chars ...byte) (tk *Token) {
tk = NewToken(self.translate(sym), string(chars))
tk = NewToken(self.row, self.column, self.translate(sym), string(chars))
return
}
func (self *scanner) makeKeywordToken(sym Symbol, upperCaseKeyword string) (tk *Token) {
tk = NewToken(self.translate(sym), upperCaseKeyword)
tk = NewToken(self.row, self.column, self.translate(sym), upperCaseKeyword)
return
}
func (self *scanner) makeValueToken(sym Symbol, source string, value any) (tk *Token) {
tk = NewValueToken(self.translate(sym), source, value)
tk = NewValueToken(self.row, self.column, self.translate(sym), source, value)
return
}
+5 -2
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// scanner_test.go
package expr
@@ -33,7 +36,7 @@ func TestScanner(t *testing.T) {
/* 14 */ {`:`, SymColon, nil, nil},
/* 15 */ {`;`, SymSemiColon, nil, nil},
/* 16 */ {`.`, SymDot, nil, nil},
/* 17 */ {`.5`, SymFloat, float64(0.5), nil},
/* 17 */ {`0.5`, SymFloat, float64(0.5), nil},
/* 18 */ {`\\`, SymBackSlash, nil, nil},
/* 19 */ {"`", SymBackTick, nil, nil},
/* 20 */ {"?", SymQuestion, nil, nil},
@@ -71,7 +74,7 @@ func TestScanner(t *testing.T) {
scanner := NewScanner(r, nil)
if tk := scanner.Next(); tk == nil {
t.Errorf("%d: %q -> got = (nil), want %v (value %v [%T)", i+1, input.source, input.wantSym, input.wantValue, input.wantValue)
t.Errorf("%d: %q -> got = (nil), want %v (value %v [%T])", i+1, input.source, input.wantSym, input.wantValue, input.wantValue)
} else if tk.Sym != input.wantSym || tk.Value != input.wantValue {
if tk.Sym == SymError && input.wantSym == tk.Sym {
if tkErr, tkOk := tk.Value.(error); tkOk {
+84
View File
@@ -0,0 +1,84 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// simple-func-store.go
package expr
import "fmt"
type SimpleFuncStore struct {
SimpleVarStore
funcStore map[string]*funcInfo
}
type funcInfo struct {
name string
minArgs int
maxArgs int
functor Functor
}
func (info *funcInfo) Name() string {
return info.name
}
func (info *funcInfo) MinArgs() int {
return info.minArgs
}
func (info *funcInfo) MaxArgs() int {
return info.maxArgs
}
func (info *funcInfo) Functor() Functor {
return info.functor
}
func NewSimpleFuncStore() *SimpleFuncStore {
ctx := &SimpleFuncStore{
SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
funcStore: make(map[string]*funcInfo),
}
ImportBuiltinsFuncs(ctx)
return ctx
}
func (ctx *SimpleFuncStore) Clone() ExprContext {
return &SimpleFuncStore{
SimpleVarStore: SimpleVarStore{varStore: CloneMap(ctx.varStore)},
funcStore: CloneMap(ctx.funcStore),
}
}
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
info, exists = ctx.funcStore[name]
return
}
func (ctx *SimpleFuncStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
ctx.funcStore[name] = &funcInfo{name: name, minArgs: minArgs, maxArgs: maxArgs, functor: functor}
}
func (ctx *SimpleFuncStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
funcNames = make([]string, 0)
for name := range ctx.funcStore {
if acceptor != nil {
if acceptor(name) {
funcNames = append(funcNames, name)
}
} else {
funcNames = append(funcNames, name)
}
}
return
}
func (ctx *SimpleFuncStore) Call(name string, args []any) (result any, err error) {
if info, exists := ctx.funcStore[name]; exists {
functor := info.functor
result, err = functor.Invoke(ctx, name, args)
} else {
err = fmt.Errorf("unknown function %s()", name)
}
return
}
+70
View File
@@ -0,0 +1,70 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// simple-var-store.go
package expr
import "fmt"
type SimpleVarStore struct {
varStore map[string]any
}
func NewSimpleVarStore() *SimpleVarStore {
return &SimpleVarStore{
varStore: make(map[string]any),
}
}
func (ctx *SimpleVarStore) Clone() (clone ExprContext) {
clone = &SimpleVarStore{
varStore: CloneMap(ctx.varStore),
}
return clone
}
func (ctx *SimpleVarStore) GetVar(varName string) (v any, exists bool) {
v, exists = ctx.varStore[varName]
return
}
func (ctx *SimpleVarStore) setVar(varName string, value any) {
ctx.varStore[varName] = value
}
func (ctx *SimpleVarStore) SetVar(varName string, value any) {
if allowedValue, ok := fromGenericAny(value); ok {
ctx.varStore[varName] = allowedValue
} else {
panic(fmt.Errorf("unsupported type %T of value %v", value, value))
}
}
func (ctx *SimpleVarStore) EnumVars(acceptor func(name string) (accept bool)) (varNames []string) {
varNames = make([]string, 0)
for name := range ctx.varStore {
if acceptor != nil {
if acceptor(name) {
varNames = append(varNames, name)
}
} else {
varNames = append(varNames, name)
}
}
return
}
func (ctx *SimpleVarStore) GetFuncInfo(name string) (f ExprFunc, exists bool) {
return
}
func (ctx *SimpleVarStore) Call(name string, args []any) (result any, err error) {
return
}
func (ctx *SimpleVarStore) RegisterFunc(name string, functor Functor, minArgs, maxArgs int) {
}
func (ctx *SimpleVarStore) EnumFuncs(acceptor func(name string) (accept bool)) (funcNames []string) {
return
}
+66 -33
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// Symbol.go
package expr
@@ -24,32 +27,44 @@ const (
SymColon // 16: ':'
SymSemiColon // 17: ';'
SymDot // 18: '.'
SymQuote // 19: '\''
SymDoubleQuote // 20: '"'
SymBackTick // 0: '`'
SymExclamation // 0: '!'
SymQuestion // 0: '?'
SymAmpersand // 0: '&&'
SymDoubleAmpersand // 0: '&&'
SymPercent // 0: '%'
SymAt // 0: '@'
SymUndescore // 0: '_'
SymEqual // 0: '='
SymDoubleEqual // 0: '=='
SymLess // 0: '<'
SymLessOrEqual // 0: '<='
SymGreater // 0: '>'
SymGreaterOrEqual // 0: '>='
SymLessGreater // 0: '<>'
SymNotEqual // 0: '!='
SymDollar // 0: '$'
SymHash // 0: '#'
SymOpenRound // 0: '('
SymClosedRound // 0: ')'
SymOpenSquare // 0: '['
SymClosedSquare // 0: ']'
SymOpenBrace // 0: '{'
SymClosedBrace // 0: '}'
SymDotSlash // 19: './'
SymQuote // 20: '\''
SymDoubleQuote // 21: '"'
SymBackTick // 22: '`'
SymExclamation // 23: '!'
SymQuestion // 24: '?'
SymAmpersand // 25: '&&'
SymDoubleAmpersand // 26: '&&'
SymPercent // 27: '%'
SymAt // 28: '@'
SymUndescore // 29: '_'
SymEqual // 30: '='
SymDoubleEqual // 31: '=='
SymLess // 32: '<'
SymLessOrEqual // 33: '<='
SymGreater // 34: '>'
SymGreaterOrEqual // 35: '>='
SymLessGreater // 36: '<>'
SymNotEqual // 37: '!='
SymDollar // 38: '$'
SymHash // 39: '#'
SymOpenRound // 40: '('
SymClosedRound // 41: ')'
SymOpenSquare // 42: '['
SymClosedSquare // 43: ']'
SymOpenBrace // 44: '{'
SymClosedBrace // 45: '}'
SymTilde // 46: '~'
SymDoubleQuestion // 47: '??'
SymQuestionEqual // 48: '?='
SymDoubleAt // 49: '@@'
SymDoubleColon // 50: '::'
SymInsert // 51: '>>'
SymAppend // 52: '<<'
SymCaret // 53: '^'
SymDollarRound // 54: '$('
SymOpenClosedRound // 55: '()'
SymDoubleDollar // 56: '$$
SymChangeSign
SymUnchangeSign
SymIdentifier
@@ -57,17 +72,31 @@ const (
SymInteger
SymFloat
SymString
SymKwAnd
SymKwNot
SymKwOr
SymIterator
SymOr
SymAnd
SymNot
SymComment
SymFunction
SymFuncCall
SymFuncDef
SymList
SymDict
SymExpression
SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
// SymOpenComment // 0: '/*'
// SymClosedComment // 0: '*/'
// SymOneLineComment // 0: '//'
keywordBase
)
const (
SymKwAnd = keywordBase + iota
SymKwNot
SymKwOr
SymKwBut
SymKwFunc
SymKwBuiltin
SymKwNil
)
var keywords map[string]Symbol
@@ -75,8 +104,12 @@ var keywords map[string]Symbol
func init() {
//keywords = make(map[string]Symbol)
keywords = map[string]Symbol{
"AND": SymKwAnd,
"OR": SymKwOr,
"NOT": SymKwNot,
"AND": SymKwAnd,
"BUILTIN": SymKwBuiltin,
"BUT": SymKwBut,
"FUNC": SymKwFunc,
"NOT": SymKwNot,
"OR": SymKwOr,
"NIL": SymKwNil,
}
}
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// op-registry.go
package expr
+60 -64
View File
@@ -1,44 +1,32 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// term.go
package expr
import (
"fmt"
"strings"
)
type termKind uint16
const (
kindUnknown termKind = iota
kindBool
kindInteger
kindFloat
kindString
kindIdentifier
)
type termClass uint16
const (
classNull termClass = iota
classConst
classVar
classFunc
classOperator
)
type termPriority uint32
const (
priNone termPriority = iota
priBut
priAssign
priOr
priAnd
priNot
priRelational
priSum
priProduct
priSelector
priSign
priFact
priIterValue
priCoalesce
priPrePost
priDot
priValue
)
@@ -49,27 +37,16 @@ const (
posInfix
posPrefix
posPostfix
posMultifix
)
type evalFuncType func(ctx exprContext, self *term) (v any, err error)
// type iterm interface {
// getClass() termClass
// getKind() termKind
// compute(ctx exprContext) (v any, err error)
// // isOperator() bool
// // isOperand() bool
// source() string
// setParent(parentNode term)
// }
type evalFuncType func(ctx ExprContext, self *term) (v any, err error)
type term struct {
tk Token
class termClass
kind termKind
parent *term
children []*term
position termPosition // operator position: infix, prefix, suffix
position termPosition // operator position: leaf, infix, prefix, postfix, multifix
priority termPriority // operator priority: higher value means higher priority
evalFunc evalFuncType
}
@@ -141,14 +118,6 @@ func (self *term) isLeaf() bool {
return self.position == posLeaf
}
// func (self *term) getKind() termKind {
// return self.kind
// }
// func (self *term) getClass() termClass {
// return self.class
// }
func (self *term) getPriority() termPriority {
return self.priority
}
@@ -160,59 +129,86 @@ func (self *term) setParent(parent *term) {
}
}
// func (self *term) isOperand() bool {
// return self.getClass() != classOperator
// }
// func (self *term) isOperator() bool {
// return self.getClass() == classOperator
// }
func (self *term) symbol() Symbol {
return self.tk.Sym
}
func (self *term) source() string {
return self.tk.source
}
func (self *term) compute(ctx exprContext) (v any, err error) {
func (self *term) value() any {
return self.tk.Value
}
func (self *term) compute(ctx ExprContext) (v any, err error) {
if self.evalFunc == nil {
err = fmt.Errorf("undfined eval-func for %v term type", self.kind)
err = self.tk.Errorf("undefined eval-func for %q term", self.source())
} else {
v, err = self.evalFunc(ctx, self)
}
return
}
func (self *term) toInt(computedValue any, valueDescription string) (i int, err error) {
if index64, ok := computedValue.(int64); ok {
i = int(index64)
} else {
err = self.Errorf("%s, got %T", valueDescription, computedValue)
}
return
}
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
return fmt.Errorf(
"left operand '%v' [%T] is not compatible with right operand '%v' [%T] with respect to operator %q",
return self.tk.Errorf(
"left operand '%v' [%T] and right operand '%v' [%T] are not compatible with operator %q",
leftValue, leftValue,
rightValue, rightValue,
self.source())
}
func (self *term) errIncompatibleType(value any) error {
return fmt.Errorf(
"prefix/postfix operator %q do not support operand '%v' [%T]", self.source(), value, value)
return self.tk.Errorf(
"prefix/postfix operator %q do not support operand '%v' [%T]",
self.source(), value, value)
}
func (self *term) Errorf(template string, args ...any) (err error) {
err = self.tk.Errorf(template, args...)
return
}
func (self *term) checkOperands() (err error) {
switch self.position {
case posInfix:
if self.children == nil || len(self.children) != 2 || self.children[0] == nil || self.children[1] == nil {
err = fmt.Errorf("infix operator %q requires two operands, got %d", self.source(), self.getChildrenCount())
if self.children == nil || len(self.children) != 2 || self.anyChildrenNil() {
err = self.tk.Errorf("infix operator %q requires two non-nil operands, got %d", self.source(), self.getChildrenCount())
}
case posPrefix:
if self.children == nil || len(self.children) != 1 || self.children[0] == nil {
err = fmt.Errorf("prefix operator %q requires one operand", self.tk.String())
err = self.tk.Errorf("prefix operator %q requires one not nil operand", self.tk.String())
}
case posPostfix:
if self.children == nil || len(self.children) != 1 || self.children[0] == nil {
err = fmt.Errorf("postfix operator %q requires one operand", self.tk.String())
if self.children == nil || len(self.children) != 1 || self.anyChildrenNil() {
err = self.tk.Errorf("postfix operator %q requires one not nil operand", self.tk.String())
}
case posMultifix:
if self.children == nil || len(self.children) < 3 || self.anyChildrenNil() {
err = self.tk.Errorf("infix operator %q requires at least three not operands, got %d", self.source(), self.getChildrenCount())
}
}
return
}
func (self *term) evalInfix(ctx exprContext) (leftValue, rightValue any, err error) {
func (self *term) anyChildrenNil() bool {
for _, child := range self.children {
if child == nil {
return true
}
}
return false
}
func (self *term) evalInfix(ctx ExprContext) (leftValue, rightValue any, err error) {
if err = self.checkOperands(); err == nil {
if leftValue, err = self.children[0].compute(ctx); err == nil {
rightValue, err = self.children[1].compute(ctx)
@@ -221,7 +217,7 @@ func (self *term) evalInfix(ctx exprContext) (leftValue, rightValue any, err err
return
}
func (self *term) evalPrefix(ctx exprContext) (rightValue any, err error) {
func (self *term) evalPrefix(ctx ExprContext) (rightValue any, err error) {
if err = self.checkOperands(); err == nil {
rightValue, err = self.children[0].compute(ctx)
}
+11 -9
View File
@@ -1,40 +1,42 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// term_test.go
package expr
import (
"fmt"
"testing"
)
func TestString(t *testing.T) {
tk1 := NewValueToken(SymInteger, "100", 100)
tk2 := NewToken(SymPlus, "+")
tk3 := NewValueToken(SymInteger, "50", 500)
tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
tk2 := NewToken(0, 0, SymPlus, "+")
tk3 := NewValueToken(0, 0, SymInteger, "50", 500)
tree := NewAst()
if gotErr := tree.addTokens(tk1, tk2, tk3); gotErr == nil {
fmt.Println("Tree:", tree)
t.Log("Tree:", tree)
} else {
t.Errorf("err: got <%v>, want <nil>", gotErr)
}
}
func TestGetRoom(t *testing.T) {
tk1 := NewValueToken(SymInteger, "100", 100)
tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
tree := NewAst()
if gotErr := tree.addTokens(tk1); gotErr == nil {
fmt.Println("Tree-root room:", tree.root.getRoom())
t.Log("Tree-root room:", tree.root.getRoom())
} else {
t.Errorf("err: got <%v>, want <nil>", gotErr)
}
}
func TestGetChildrenCount(t *testing.T) {
tk1 := NewValueToken(SymInteger, "100", 100)
tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
tree := NewAst()
if gotErr := tree.addTokens(tk1); gotErr == nil {
fmt.Println("Tree-root children count:", tree.root.getChildrenCount())
t.Log("Tree-root children count:", tree.root.getChildrenCount())
} else {
t.Errorf("err: got <%v>, want <nil>", gotErr)
}
+2
View File
@@ -0,0 +1,2 @@
uno
due
+15
View File
@@ -0,0 +1,15 @@
/*
test-funcs.expr: example source file
*/
// double(x): returns 2*x
@double=func(x){2*x};
// Define variable 'a' wth value 5
@a=5;
// two(): returns 2
@two=func() {2};
// six(): returns 6
six=func() {6};
+35 -7
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// token.go
package expr
@@ -8,6 +11,8 @@ import (
)
type Token struct {
row int
col int
Sym Symbol
source string
Value any
@@ -22,24 +27,28 @@ func (tk *Token) DevString() string {
func (tk *Token) String() string {
if tk.Value != nil {
return fmt.Sprintf("%#v", tk.Value)
if s, ok := tk.Value.(string); ok {
return fmt.Sprintf("%q", s)
} else {
return fmt.Sprintf("%v", tk.Value)
}
}
return fmt.Sprintf("%s", tk.source)
}
func NewToken(sym Symbol, source string) *Token {
return &Token{Sym: sym, source: source}
func NewToken(row, col int, sym Symbol, source string) *Token {
return &Token{row: row, col: col, Sym: sym, source: source}
}
func NewValueToken(sym Symbol, source string, value any) *Token {
return &Token{Sym: sym, source: source, Value: value}
func NewValueToken(row, col int, sym Symbol, source string, value any) *Token {
return &Token{row: row, col: col, Sym: sym, source: source, Value: value}
}
func NewErrorToken(row, col int, err error) *Token {
if err == io.EOF {
return NewToken(SymEos, "")
return NewToken(row, col, SymEos, "")
}
return NewValueToken(SymError, fmt.Sprintf("[%d:%d]", row, col), err)
return NewValueToken(row, col, SymError, fmt.Sprintf("[%d:%d]", row, col), err)
}
func (tk *Token) IsEos() bool {
@@ -53,3 +62,22 @@ func (tk *Token) IsError() bool {
func (tk *Token) IsTerm(termSymbols []Symbol) bool {
return tk.IsEos() || tk.IsError() || (termSymbols != nil && slices.Index(termSymbols, tk.Sym) >= 0)
}
func (self *Token) Errorf(template string, args ...any) (err error) {
err = fmt.Errorf(fmt.Sprintf("[%d:%d] ", self.row, self.col)+template, args...)
return
}
func (self *Token) Error() (err error) {
if self.Sym == SymError {
if msg, ok := self.Value.(error); ok {
err = fmt.Errorf("[%d:%d] %v", self.row, self.col, msg)
}
}
return
}
func (self *Token) Errors(msg string) (err error) {
err = fmt.Errorf("[%d:%d] %v", self.row, self.col, msg)
return
}
+28 -4
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// token_test.go
package expr
@@ -7,9 +10,30 @@ import (
)
func TestDevString(t *testing.T) {
tk1 := NewValueToken(SymInteger, "100", 100)
tk2 := NewToken(SymPlus, "+")
type inputType struct {
source string
sym Symbol
value any
wantResult string
}
fmt.Println("Token '100':", tk1.DevString())
fmt.Println("Token '+':", tk2.DevString())
inputs := []inputType{
/* 1 */ {"100", SymInteger, 100, fmt.Sprintf(`[%d]"100"{100}`, SymInteger)},
/* 2 */ {"+", SymPlus, nil, fmt.Sprintf(`[%d]"+"{}`, SymPlus)},
}
for i, input := range inputs {
var tk *Token
if input.value == nil {
tk = NewToken(0, 0, input.sym, input.source)
} else {
tk = NewValueToken(0, 0, input.sym, input.source, input.value)
}
t.Logf("Test nr %2d: %q --> %q", i+1, input.source, input.wantResult)
if s := tk.DevString(); s != input.wantResult {
t.Errorf("wrong token from symbol '+': expected %q, got %q", input.wantResult, s)
}
}
}
+87
View File
@@ -1,6 +1,11 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// utils.go
package expr
import "reflect"
func isString(v any) (ok bool) {
_, ok = v.(string)
return ok
@@ -16,6 +21,16 @@ func isFloat(v any) (ok bool) {
return ok
}
func isList(v any) (ok bool) {
_, ok = v.([]any)
return ok
}
func isDict(v any) (ok bool) {
_, ok = v.(map[any]any)
return ok
}
func isNumber(v any) (ok bool) {
return isFloat(v) || isInteger(v)
}
@@ -49,3 +64,75 @@ func toBool(v any) (b bool, ok bool) {
}
return
}
func isFunc(v any) bool {
return reflect.TypeOf(v).Kind() == reflect.Func
}
func anyInteger(v any) (i int64, ok bool) {
ok = true
switch intval := v.(type) {
case int:
i = int64(intval)
case uint8:
i = int64(intval)
case uint16:
i = int64(intval)
case uint64:
i = int64(intval)
case uint32:
i = int64(intval)
case int8:
i = int64(intval)
case int16:
i = int64(intval)
case int32:
i = int64(intval)
case int64:
i = intval
default:
ok = false
}
return
}
func fromGenericAny(v any) (exprAny any, ok bool) {
if exprAny, ok = v.(bool); ok {
return
}
if exprAny, ok = v.(string); ok {
return
}
if exprAny, ok = anyInteger(v); ok {
return
}
if exprAny, ok = anyFloat(v); ok {
return
}
return
}
func anyFloat(v any) (float float64, ok bool) {
ok = true
switch floatval := v.(type) {
case float32:
float = float64(floatval)
case float64:
float = floatval
default:
ok = false
}
return
}
func CopyMap[K comparable, V any](dest, source map[K]V) map[K]V {
for k, v := range source {
dest[k] = v
}
return dest
}
func CloneMap[K comparable, V any](source map[K]V) map[K]V {
dest := make(map[K]V, len(source))
return CopyMap(dest, source)
}