Compare commits

...

63 Commits

Author SHA1 Message Date
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
48 changed files with 2795 additions and 519 deletions
+366
View File
@@ -0,0 +1,366 @@
= 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::[]
== 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)
}
}
----
=== 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
#TODO: variables#
=== 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
==== Function calls
#TODO: function calls operations#
==== Function definitions
#TODO: function definitions operations#
==== Builtins
#TODO: builtins#
View File
+56 -5
View File
@@ -1,15 +1,23 @@
// 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)
}
//-------- ast
type ast struct {
forest []*term
root *term
}
@@ -17,6 +25,16 @@ 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
}
@@ -69,14 +92,42 @@ 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 {
if preset {
initDefaultVars(ctx)
}
if self.forest != nil {
for _, root := range self.forest {
if result, err = root.compute(ctx); err == nil {
ctx.setVar(control_last_result, 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")
}
+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
+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) ExprFunc
Call(name string, args []any) (result any, err error)
RegisterFunc(name string, f Functor, minArgs, maxArgs int)
}
+59
View File
@@ -0,0 +1,59 @@
// preset.go
package expr
import "strings"
// Preset control variables
const (
control_last_result = "_last"
control_bool_shortcut = "_bool_shortcut"
control_import_path = "_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(control_bool_shortcut, true)
ctx.setVar(control_import_path, 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
}
+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

+139
View File
@@ -0,0 +1,139 @@
// 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, control_import_path); 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 importImportFunc(ctx ExprContext) {
ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1)
ctx.RegisterFunc("include", &simpleFunctor{f: includeFunc}, 1, -1)
}
+107
View File
@@ -0,0 +1,107 @@
// 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 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)
}
+3
View File
@@ -0,0 +1,3 @@
module git.portale-stac.it/go-pkg/expr
go 1.21.6
+58
View File
@@ -0,0 +1,58 @@
// 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
}
+80
View File
@@ -0,0 +1,80 @@
// 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)
}
fmt.Println("Hello World!")
}
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)
}
fmt.Println("Hello World!")
}
+55
View File
@@ -0,0 +1,55 @@
// 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
}
+37
View File
@@ -0,0 +1,37 @@
// iterator.go
package expr
import "io"
type Iterator interface {
Reset()
Next() (item any, err error) // must return io.EOF after the last item
Index() int
}
type FlatArrayIterator struct {
a []any
index int
}
func NewFlatArrayIterator(array []any) *FlatArrayIterator {
return &FlatArrayIterator{a: array, index: 0}
}
func (it *FlatArrayIterator) Reset() {
it.index = 0
}
func (it *FlatArrayIterator) Next() (item any, err error) {
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
}
+10 -9
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-const.go
package expr
@@ -5,8 +8,8 @@ package expr
func newBoolTerm(tk *Token) *term {
return &term{
tk: *tk,
class: classConst,
kind: kindBool,
// class: classConst,
// kind: kindBool,
parent: nil,
children: nil,
position: posLeaf,
@@ -19,8 +22,6 @@ func newBoolTerm(tk *Token) *term {
func newIntegerTerm(tk *Token) *term {
return &term{
tk: *tk,
class: classConst,
kind: kindInteger,
parent: nil,
children: nil,
position: posLeaf,
@@ -33,8 +34,8 @@ func newIntegerTerm(tk *Token) *term {
func newFloatTerm(tk *Token) *term {
return &term{
tk: *tk,
class: classConst,
kind: kindFloat,
// class: classConst,
// kind: kindFloat,
parent: nil,
children: nil,
position: posLeaf,
@@ -47,8 +48,8 @@ func newFloatTerm(tk *Token) *term {
func newStringTerm(tk *Token) *term {
return &term{
tk: *tk,
class: classConst,
kind: kindString,
// class: classConst,
// kind: kindString,
parent: nil,
children: nil,
position: posLeaf,
@@ -58,7 +59,7 @@ 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
}
+34
View File
@@ -0,0 +1,34 @@
// 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
}
// init
// func init() {
// registerTermConstructor(SymExpression, newExprTerm)
// }
+97 -8
View File
@@ -1,22 +1,30 @@
// 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,
// 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 := parentCtx.Clone()
name, _ := self.tk.Value.(string)
params := make([]any, len(self.children))
for i, tree := range self.children {
@@ -27,7 +35,88 @@ 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 {
exportAll := isEnabled(ctx, control_export_all)
// Export variables
for _, refName := range ctx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
refValue, _ := ctx.GetVar(refName)
exportVar(parentCtx, refName, refValue)
}
// Export functions
for _, refName := range ctx.EnumFuncs(func(name string) bool { return exportAll || name[0] == '@' }) {
if info := ctx.GetFuncInfo(refName); info != nil {
exportFunc(parentCtx, refName, info)
}
}
}
}
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())
}
// -------- 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) {
ctx.setVar(p, args[i])
} 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())
// if paramName, ok := param.value().(string); ok {
// paramList = append(paramList, paramName)
// } else {
// err = fmt.Errorf("invalid function definition: formal param nr %d must be an identifier", i+1)
// break
// }
}
v = &funcDefFunctor{
params: paramList,
expr: expr,
}
} else {
err = errors.New("invalid function definition: the body specification must be an expression")
}
return
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-list.go
package expr
// -------- 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
}
+35
View File
@@ -0,0 +1,35 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-selector-case.go
package expr
import "fmt"
// -------- selector case term
type selectorCase struct {
filterList *term
caseExpr Expr
}
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
}
+7 -4
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-var.go
package expr
@@ -7,8 +10,8 @@ import "fmt"
func newVarTerm(tk *Token) *term {
return &term{
tk: *tk,
class: classVar,
kind: kindUnknown,
// class: classVar,
// kind: kindUnknown,
parent: nil,
children: nil,
position: posLeaf,
@@ -18,9 +21,9 @@ 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 {
if v, exists = ctx.GetVar(self.tk.source); !exists {
err = fmt.Errorf("undefined variable %q", self.tk.source)
}
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.tk.source, v)
}
}
return
}
// init
func init() {
registerTermConstructor(SymEqual, newAssignTerm)
}
+82 -9
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 {
@@ -35,8 +38,8 @@ func evalNot(ctx exprContext, self *term) (v any, err error) {
func newAndTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
// 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, control_bool_shortcut) {
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,
// 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, control_bool_shortcut) {
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)
+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 _, ok := rightValue.(Functor); ok {
err = errCoalesceNoFunc(self.children[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)
}
+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)
}
+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 {
+78 -6
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-prod.go
package expr
@@ -11,8 +14,8 @@ import (
func newMultiplyTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
// 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 {
@@ -50,8 +53,8 @@ func evalMultiply(ctx exprContext, self *term) (v any, err error) {
func newDivideTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
// 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)
}
+19 -18
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-rel.go
package expr
@@ -6,8 +9,8 @@ package expr
func newEqualTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
// 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 {
@@ -45,8 +48,8 @@ func evalEqual(ctx exprContext, self *term) (v any, err error) {
func newNotEqualTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
// 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
@@ -92,8 +95,8 @@ func evalNotEqual(ctx exprContext, self *term) (v any, err error) {
func newLessTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
// 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 {
@@ -170,8 +171,8 @@ func evalLessEqual(ctx exprContext, self *term) (v any, err error) {
func newGreaterTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
// 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
@@ -192,8 +193,8 @@ func evalGreater(ctx exprContext, self *term) (v any, err error) {
func newGreaterEqualTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindBool,
// 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
+68
View File
@@ -0,0 +1,68 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-selector.go
package expr
//-------- export all term
func newSelectorTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 3),
position: posMultifix,
priority: priSelector,
evalFunc: evalSelector,
}
}
func isSelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (selectedValue any, err error) {
caseData, _ := caseSel.(*selectorCase)
if caseData.filterList == nil {
selectedValue, err = caseData.caseExpr.eval(ctx, false)
} else {
filterList := caseData.filterList.children
if len(filterList) == 0 && exprValue == int64(caseIndex) {
selectedValue, err = caseData.caseExpr.eval(ctx, false)
} 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)
break
}
}
}
}
return
}
func evalSelector(ctx ExprContext, self *term) (v any, err error) {
var exprValue any
// var caseList []*term
if err = self.checkOperands(); err != nil {
return
}
exprTerm := self.children[0]
if exprValue, err = exprTerm.compute(ctx); err != nil {
return
}
caseList := self.children[1:]
for i, caseTerm := range caseList {
caseSel := caseTerm.value()
if v, err = isSelectorCase(ctx, exprValue, caseSel, i); err != nil || v != nil {
break
}
}
if err == nil && v == nil {
err = exprTerm.tk.Errorf("no case catches the value (%v) of the selection expression", exprValue)
}
return
}
// init
func init() {
registerTermConstructor(SymSelector, newSelectorTerm)
}
+6 -5
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-sign.go
package expr
@@ -6,8 +9,8 @@ package expr
func newPlusSignTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
// 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 {
+35 -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,8 @@ func evalPlus(ctx exprContext, self *term) (v any, err error) {
func newMinusTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
class: classOperator,
kind: kindUnknown,
// class: classOperator,
// kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priSum,
@@ -56,7 +75,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 +90,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)
}
+254 -42
View File
@@ -1,63 +1,47 @@
// 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)
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.parse(scanner, SymComma, SymClosedRound); err == nil {
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil {
if subTree.root != nil {
args = append(args, subTree.root)
}
} else {
break
}
@@ -65,12 +49,240 @@ func (self *parser) parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, e
}
if err == nil {
// TODO Check arguments
t := newFuncTerm(tk, args)
tree.addTerm(t)
}
if lastSym != SymClosedRound {
err = errors.New("unterminate arguments list")
} else {
err = tree.addToken(tk)
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) parseList(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
args := make([]*term, 0)
lastSym := SymUnknown
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 {
break
}
lastSym = scanner.Previous().Sym
}
if err == nil {
// TODO Check arguments
if lastSym != SymClosedSquare {
err = scanner.Previous().Errorf("unterminate items list")
} else {
subtree = newListTerm(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 (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 {
selectorTerm.children = append(selectorTerm.children, caseTerm)
caseTerm.parent = selectorTerm
}
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
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
}
//resetSelector := true
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 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 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 != nil {
err = tk.Errorf("nested selectors must be enclosed in parentheses")
} else*/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 {
selectorTerm.children = append(selectorTerm.children, caseTerm)
caseTerm.parent = selectorTerm
currentTerm = caseTerm
if tk.Sym == SymDoubleColon {
selectorTerm = nil
}
}
//resetSelector = tk.Sym == SymDoubleColon
default:
currentTerm, err = tree.addToken2(tk)
}
if currentTerm != nil && currentTerm.parent != nil && currentTerm.parent.tk.Sym != SymSelector {
selectorTerm = nil
}
// if resetSelector {
// selectorTree = nil
// }
lastSym = tk.Sym
}
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
}
+217 -116
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// parser_test.go
package expr
@@ -8,89 +11,6 @@ import (
"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,15 +18,7 @@ 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},
@@ -140,18 +52,18 @@ func TestParser(t *testing.T) {
/* 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"`)},
/* 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},
/* 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]`)},
/* 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(`infix operator "+" requires two operands, got 1`)},
/* 45 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two not nil 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`)},
@@ -166,50 +78,239 @@ func TestParser(t *testing.T) {
/* 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`)},
/* 60 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two not nil operands, got 1`)},
/* 61 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two not nil operands, got 1`)},
/* 62 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two not nil operands, got 1`)},
/* 63 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two not nil operands, got 1`)},
/* 64 */ {"1.5 != ", nil, errors.New(`[1:6] infix operator "!=" requires two not nil operands, got 1`)},
/* 65 */ {"1.5 == ", nil, errors.New(`[1:6] infix operator "==" requires two not nil operands, got 1`)},
/* 66 */ {`"1.5" < `, nil, errors.New(`[1:8] infix operator "<" requires two not nil operands, got 1`)},
/* 67 */ {`"1.5" > `, nil, errors.New(`[1:8] infix operator ">" requires two not nil operands, got 1`)},
/* 68 */ {`"1.5" == `, nil, errors.New(`[1:8] infix operator "==" requires two not nil operands, got 1`)},
/* 69 */ {`"1.5" != `, nil, errors.New(`[1:8] infix operator "!=" requires two not nil operands, got 1`)},
/* 70 */ {"+1.5", float64(1.5), nil},
/* 71 */ {"+", nil, errors.New(`prefix operator "+" requires one operand`)},
/* 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 "x"`)},
/* 100 */ {`false or true`, true, nil},
/* 101 */ {`false or (x==2)`, nil, errors.New(`undefined variable "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 */ {`x ?? func(){}"`, nil, errors.New(`[1:15] the right operand of a coalescing operation cannot be a function definition`)},
/* 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 "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},
}
check_env_expr_path := 113
succeeded := 0
failed := 0
// inputs1 := []inputType{
// {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
// }
parser := NewParser(ctx)
for i, input := range inputs {
var expr *ast
var expr Expr
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)
importImportFunc(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 {
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 environment variable with value "."`, 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
}
// inputs1 := []inputType{
// {`add(1,2,3)`, int64(6), nil},
// }
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
for i, input := range inputs {
var expr *ast
var gotResult any
var gotErr error
ctx := NewSimpleFuncStore()
ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc")
importMathFuncs(ctx)
parser := NewParser(ctx)
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good := true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
if (gotResult == nil && input.wantResult != nil) || (gotResult != nil && input.wantResult == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotList, okGot := gotResult.([]any); okGot {
if wantList, okWant := input.wantResult.([]any); okWant {
if (gotList == nil && wantList != nil) || (gotList != nil && wantList == nil) {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
} else {
equal := len(gotList) == len(wantList)
if equal {
for i, gotItem := range gotList {
wantItem := wantList[i]
equal = gotItem == wantItem
if !equal {
break
}
}
}
if !equal {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
}
}
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
good = false
}
}
if good {
succeeded++
} else {
failed++
}
}
t.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};
+42 -74
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,
}
@@ -139,12 +144,18 @@ func (self *scanner) fetchNextToken() (tk *Token) {
case ',':
tk = self.makeToken(SymComma, ch)
case ':':
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)
} else if next == '/' {
tk = self.moveOn(SymDotSlash, ch, next)
} else {
tk = self.makeToken(SymDot, ch)
}
@@ -166,7 +177,13 @@ func (self *scanner) fetchNextToken() (tk *Token) {
tk = self.makeToken(SymExclamation, ch)
}
case '?':
if next, _ := self.peek(); next == '?' {
tk = self.moveOn(SymDoubleQuestion, ch, next)
} else if next, _ := self.peek(); next == '=' {
tk = self.moveOn(SymQuestionEqual, ch, next)
} else {
tk = self.makeToken(SymQuestion, ch)
}
case '&':
if next, _ := self.peek(); next == '&' {
tk = self.moveOn(SymDoubleAmpersand, ch, next)
@@ -178,7 +195,19 @@ func (self *scanner) fetchNextToken() (tk *Token) {
case '#':
tk = self.makeToken(SymHash, ch)
case '@':
if next, _ := self.peek(); (next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z') {
self.readChar()
if tk = self.fetchIdentifier(next); tk.Sym == SymIdentifier {
//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 '=':
@@ -215,14 +244,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)
}
@@ -329,7 +364,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 +394,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 +493,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 +501,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
}
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// scanner_test.go
package expr
+79
View File
@@ -0,0 +1,79 @@
// 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 {
return &SimpleFuncStore{
SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
funcStore: make(map[string]*funcInfo),
}
}
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) {
info, _ = 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
}
+67
View File
@@ -0,0 +1,67 @@
// 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) {
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
}
+52 -31
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,38 @@ 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: '::'
SymChangeSign
SymUnchangeSign
SymIdentifier
@@ -57,17 +66,27 @@ const (
SymInteger
SymFloat
SymString
SymKwAnd
SymKwNot
SymKwOr
SymOr
SymAnd
SymNot
SymComment
SymFunction
SymFuncCall
SymFuncDef
SymList
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
)
var keywords map[string]Symbol
@@ -76,7 +95,9 @@ func init() {
//keywords = make(map[string]Symbol)
keywords = map[string]Symbol{
"AND": SymKwAnd,
"OR": SymKwOr,
"BUT": SymKwBut,
"FUNC": SymKwFunc,
"NOT": SymKwNot,
"OR": SymKwOr,
}
}
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// op-registry.go
package expr
+45 -65
View File
@@ -1,44 +1,29 @@
// 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
priCoalesce
priValue
)
@@ -49,27 +34,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 +115,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,21 +126,17 @@ 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) 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)
}
@@ -182,37 +144,55 @@ func (self *term) compute(ctx exprContext) (v any, err error) {
}
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 not 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 +201,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)
}
+8 -5
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// term_test.go
package expr
@@ -7,9 +10,9 @@ import (
)
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 {
@@ -20,7 +23,7 @@ func TestString(t *testing.T) {
}
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 {
@@ -30,7 +33,7 @@ func TestGetRoom(t *testing.T) {
}
}
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 {
+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};
+16 -6
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
@@ -27,19 +32,19 @@ func (tk *Token) String() string {
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 +58,8 @@ 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
}
+5 -2
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// token_test.go
package expr
@@ -7,8 +10,8 @@ import (
)
func TestDevString(t *testing.T) {
tk1 := NewValueToken(SymInteger, "100", 100)
tk2 := NewToken(SymPlus, "+")
tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
tk2 := NewToken(0, 0, SymPlus, "+")
fmt.Println("Token '100':", tk1.DevString())
fmt.Println("Token '+':", tk2.DevString())
+82
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,11 @@ func isFloat(v any) (ok bool) {
return ok
}
func isList(v any) (ok bool) {
_, ok = v.([]any)
return ok
}
func isNumber(v any) (ok bool) {
return isFloat(v) || isInteger(v)
}
@@ -49,3 +59,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)
}