Compare commits

..

103 Commits

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

After

Width:  |  Height:  |  Size: 35 KiB

+72
View File
@@ -0,0 +1,72 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// expr_test.go
package expr
import (
"fmt"
"strings"
"testing"
)
func TestExpr(t *testing.T) {
type inputType struct {
source string
wantResult any
wantErr error
}
inputs := []inputType{
/* 1 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
}
succeeded := 0
failed := 0
inputs1 := []inputType{
{`f=openFile("/tmp/test2.txt"); line=readFile(f); closeFile(f); line`, "ciao", nil},
//{`f = func(op){op()}; f(func(){2})`, int64(2), nil},
}
for i, input := range inputs1 {
var expr Expr
var gotResult any
var gotErr error
ctx := NewSimpleFuncStore()
// ImportMathFuncs(ctx)
// ImportImportFunc(ctx)
ImportOsFuncs(ctx)
parser := NewParser(ctx)
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations())
good := true
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
}
if gotResult != input.wantResult {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
good = false
}
if gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
good = false
}
}
if good {
succeeded++
} else {
failed++
}
}
t.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed))
}
+142
View File
@@ -0,0 +1,142 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-import.go
package expr
import (
"errors"
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
)
const ENV_EXPR_PATH = "EXPR_PATH"
func importFunc(ctx ExprContext, name string, args []any) (result any, err error) {
return importGeneral(ctx, name, args)
}
func includeFunc(ctx ExprContext, name string, args []any) (result any, err error) {
enable(ctx, control_export_all)
return importGeneral(ctx, name, args)
}
func importGeneral(ctx ExprContext, name string, args []any) (result any, err error) {
var dirList []string
dirList = addEnvImportDirs(dirList)
dirList = addPresetImportDirs(ctx, dirList)
result, err = doImport(ctx, name, dirList, NewFlatArrayIterator(args))
return
}
func checkStringParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(isString(paramValue) /*|| isList(paramValue)*/) {
err = fmt.Errorf("%s(): param nr %d has wrong type %T, string expected", funcName, paramPos+1, paramValue)
}
return
}
func addEnvImportDirs(dirList []string) []string {
if dirSpec, exists := os.LookupEnv(ENV_EXPR_PATH); exists {
dirs := strings.Split(dirSpec, ":")
if dirList == nil {
dirList = dirs
} else {
dirList = append(dirList, dirs...)
}
}
return dirList
}
func addPresetImportDirs(ctx ExprContext, dirList []string) []string {
if dirSpec, exists := getControlString(ctx, ControlImportPath); exists {
dirs := strings.Split(dirSpec, ":")
if dirList == nil {
dirList = dirs
} else {
dirList = append(dirList, dirs...)
}
}
return dirList
}
func isFile(filePath string) bool {
info, err := os.Stat(filePath)
return (err == nil || errors.Is(err, os.ErrExist)) && info.Mode().IsRegular()
}
func searchAmongPath(filename string, dirList []string) (filePath string) {
for _, dir := range dirList {
if fullPath := path.Join(dir, filename); isFile(fullPath) {
filePath = fullPath
break
}
}
return
}
func isPathRelative(filePath string) bool {
unixPath := filepath.ToSlash(filePath)
return strings.HasPrefix(unixPath, "./") || strings.HasPrefix(unixPath, "../")
}
func makeFilepath(filename string, dirList []string) (filePath string, err error) {
if path.IsAbs(filename) || isPathRelative(filename) {
if isFile(filename) {
filePath = filename
}
} else {
filePath = searchAmongPath(filename, dirList)
}
if len(filePath) == 0 {
err = fmt.Errorf("source file %q not found", filename)
}
return
}
func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (result any, err error) {
var v any
var sourceFilepath string
for v, err = it.Next(); err == nil; v, err = it.Next() {
if err = checkStringParamExpected(name, v, it.Index()); err != nil {
break
}
if sourceFilepath, err = makeFilepath(v.(string), dirList); err != nil {
break
}
var file *os.File
if file, err = os.Open(sourceFilepath); err == nil {
defer file.Close()
var expr *ast
scanner := NewScanner(file, DefaultTranslations())
parser := NewParser(ctx)
if expr, err = parser.parseGeneral(scanner, true, true); err == nil {
result, err = expr.eval(ctx, false)
}
if err != nil {
break
}
} else {
break
}
}
if err != nil {
if err == io.EOF {
err = nil
} else {
result = nil
}
}
return
}
func ImportImportFuncs(ctx ExprContext) {
ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1)
ctx.RegisterFunc("include", &simpleFunctor{f: includeFunc}, 1, -1)
}
+114
View File
@@ -0,0 +1,114 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// funcs-math.go
package expr
import (
"fmt"
"io"
)
func checkNumberParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(isNumber(paramValue) || isList(paramValue)) {
err = fmt.Errorf("%s(): param nr %d has wrong type %T, number expected", funcName, paramPos+1, paramValue)
}
return
}
func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
var sumAsFloat = false
var floatSum float64 = 0.0
var intSum int64 = 0
var v any
for v, err = it.Next(); err == nil; v, err = it.Next() {
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
break
}
if array, ok := v.([]any); ok {
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
break
}
}
if !sumAsFloat && isFloat(v) {
sumAsFloat = true
floatSum = float64(intSum)
}
if sumAsFloat {
floatSum += numAsFloat(v)
} else {
iv, _ := v.(int64)
intSum += iv
}
}
if err == nil || err == io.EOF {
err = nil
if sumAsFloat {
result = floatSum
} else {
result = intSum
}
}
return
}
func addFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result, err = doAdd(ctx, name, NewFlatArrayIterator(args))
return
}
func doMul(ctx ExprContext, name string, it Iterator) (result any, err error) {
var mulAsFloat = false
var floatProd float64 = 1.0
var intProd int64 = 1
var v any
for v, err = it.Next(); err == nil; v, err = it.Next() {
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
break
}
if array, ok := v.([]any); ok {
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
break
}
}
if !mulAsFloat && isFloat(v) {
mulAsFloat = true
floatProd = float64(intProd)
}
if mulAsFloat {
floatProd *= numAsFloat(v)
} else {
iv, _ := v.(int64)
intProd *= iv
}
}
if err == nil || err == io.EOF {
err = nil
if mulAsFloat {
result = floatProd
} else {
result = intProd
}
}
return
}
func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
result, err = doMul(ctx, name, NewFlatArrayIterator(args))
return
}
func ImportMathFuncs(ctx ExprContext) {
ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
}
func init() {
registerImport("math.arith", ImportMathFuncs)
}
+164
View File
@@ -0,0 +1,164 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// func-os.go
package expr
import (
"bufio"
"fmt"
"io"
"os"
)
type osHandle interface {
getFile() *os.File
}
type osWriter struct {
fh *os.File
writer *bufio.Writer
}
func (h *osWriter) getFile() *os.File {
return h.fh
}
type osReader struct {
fh *os.File
reader *bufio.Reader
}
func (h *osReader) getFile() *os.File {
return h.fh
}
func createFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var filePath string
if len(args) > 0 {
filePath, _ = args[0].(string)
}
if len(filePath) > 0 {
var fh *os.File
if fh, err = os.Create(filePath); err == nil {
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
}
} else {
err = fmt.Errorf("%s(): missing the file path", name)
}
return
}
func openFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var filePath string
if len(args) > 0 {
filePath, _ = args[0].(string)
}
if len(filePath) > 0 {
var fh *os.File
if fh, err = os.Open(filePath); err == nil {
result = &osReader{fh: fh, reader: bufio.NewReader(fh)}
}
} else {
err = fmt.Errorf("%s(): missing the file path", name)
}
return
}
func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var filePath string
if len(args) > 0 {
filePath, _ = args[0].(string)
}
if len(filePath) > 0 {
var fh *os.File
if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil {
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
}
} else {
err = fmt.Errorf("%s(): missing the file path", name)
}
return
}
func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle
if len(args) > 0 {
handle, _ = args[0].(osHandle)
}
if handle != nil {
if fh := handle.getFile(); fh != nil {
if w, ok := handle.(*osWriter); ok {
err = w.writer.Flush()
}
if err == nil {
err = fh.Close()
}
}
} else {
err = fmt.Errorf("%s(): invalid file handle", name)
}
result = err == nil
return
}
func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle
if len(args) > 0 {
handle, _ = args[0].(osHandle)
}
if handle != nil {
if fh := handle.getFile(); fh != nil {
if w, ok := handle.(*osWriter); ok {
result, err = fmt.Fprint(w.writer, args[1:]...)
}
}
}
return
}
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
var handle osHandle
if len(args) > 0 {
handle, _ = args[0].(osHandle)
}
if handle != nil {
if fh := handle.getFile(); fh != nil {
if r, ok := handle.(*osReader); ok {
var limit byte = '\n'
var v string
if len(args) > 1 {
if s, ok := args[1].(string); ok && len(s) > 0 {
limit = s[0]
}
}
if v, err = r.reader.ReadString(limit); err == nil || err == io.EOF {
if len(v) > 0 && v[len(v)-1] == limit {
result = v[0 : len(v)-1]
} else {
result = v
}
}
}
}
}
return
}
func ImportOsFuncs(ctx ExprContext) {
ctx.RegisterFunc("openFile", &simpleFunctor{f: openFileFunc}, 1, 1)
ctx.RegisterFunc("appendFile", &simpleFunctor{f: appendFileFunc}, 1, 1)
ctx.RegisterFunc("createFile", &simpleFunctor{f: createFileFunc}, 1, 1)
ctx.RegisterFunc("writeFile", &simpleFunctor{f: writeFileFunc}, 1, -1)
ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2)
ctx.RegisterFunc("closeFile", &simpleFunctor{f: closeFileFunc}, 1, 1)
}
+47
View File
@@ -0,0 +1,47 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// function-register.go
package expr
import (
"path/filepath"
)
var functionRegister map[string]func(ExprContext)
func registerImport(name string, importFunc func(ExprContext)) {
if functionRegister == nil {
functionRegister = make(map[string]func(ExprContext))
}
functionRegister[name] = importFunc
}
func ImportInContext(ctx ExprContext, name string) (exists bool) {
var importFunc func(ExprContext)
if importFunc, exists = functionRegister[name]; exists {
importFunc(ctx)
}
return
}
func ImportInContextByGlobPattern(ctx ExprContext, pattern string) (count int, err error) {
var matched bool
for name, importFunc := range functionRegister {
if matched, err = filepath.Match(pattern, name); err == nil {
if matched {
count++
importFunc(ctx)
}
} else {
break
}
}
return
}
func init() {
if functionRegister == nil {
functionRegister = make(map[string]func(ExprContext))
}
}
+3
View File
@@ -0,0 +1,3 @@
module git.portale-stac.it/go-pkg/expr
go 1.21.6
+122
View File
@@ -0,0 +1,122 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// graph.go
package expr
import (
"fmt"
"strings"
)
func BuildGraph(root *term) (g string) {
//var sb strings.Builder
g = fmt.Sprintf("tree: %v", root)
r := NewReticle(root)
fmt.Println(r)
return
}
type NodeRef struct {
node *term
pos int
label string
}
type Level []*NodeRef
type Reticle struct {
levels []Level
left, right int
colsWidth []int
}
func NewExprReticle(e Expr) *Reticle {
tree, _ := e.(*ast)
return NewReticle(tree.root)
}
func NewReticle(tree *term) *Reticle {
r := &Reticle{levels: make([]Level, 0)}
r.build(tree, 0, 0)
r.computeCharWidth()
return r
}
func (r *Reticle) computeCharWidth() {
numCol := r.right - r.left + 1
r.colsWidth = make([]int, numCol)
for _, level := range r.levels {
for _, ref := range level {
c := ref.pos - r.left
if v := ref.node.value(); v != nil {
ref.label = fmt.Sprintf("%v", v)
} else {
ref.label = ref.node.source()
}
r.colsWidth[c] = max(r.colsWidth[c], len(ref.label)+2) // +2 to make room for brakets
}
}
}
func (r *Reticle) build(node *term, depth, pos int) {
var level Level
if node == nil {
return
}
if len(r.levels) == depth {
level = make(Level, 0)
r.levels = append(r.levels, level)
} else {
level = r.levels[depth]
}
ref := &NodeRef{node: node, pos: pos}
level = append(level, ref)
if r.left > pos {
r.left = pos
}
if r.right < pos {
r.right = pos
}
if node.children != nil {
halfPos := len(node.children) / 2
childPos := pos - halfPos - 1
for _, child := range node.children {
childPos++
if childPos == pos && (len(node.children)&1) == 0 {
childPos++
}
r.build(child, depth+1, childPos)
}
}
r.levels[depth] = level
}
func (r *Reticle) nodeInLevel(level []*NodeRef, index int) (node *NodeRef) {
for _, ref := range level {
if ref.pos-r.left == index {
node = ref
break
}
}
return
}
func (r *Reticle) String() string {
var sb strings.Builder
for _, level := range r.levels {
for j, w := range r.colsWidth {
var label string
if ref := r.nodeInLevel(level, j); ref != nil {
s := "(" + ref.label + ")"
label = fmt.Sprintf("%[1]*s", -w, fmt.Sprintf("%[1]*s", (w+len(s))/2, s))
} else {
label = strings.Repeat(" ", w)
}
sb.WriteString(label)
}
sb.WriteByte('\n')
}
return sb.String()
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// graph_test.go
package expr
import (
"fmt"
"testing"
)
func TestGraph(t *testing.T) {
// tk0 := NewToken(0, 0, SymChangeSign, "-")
tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
tk2 := NewToken(0, 0, SymPlus, "+")
tk3 := NewValueToken(0, 0, SymInteger, "200", 200)
tree := NewAst()
// tree.addToken(tk0)
tree.addToken(tk1)
tree.addToken(tk2)
tree.addToken(tk3)
tk4 := NewToken(0, 0, SymPlus, "-")
tk5 := NewValueToken(0, 0, SymInteger, "50", 50)
tree.addToken(tk4)
tree.addToken(tk5)
g := BuildGraph(tree.root)
fmt.Println(g)
}
+61
View File
@@ -0,0 +1,61 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// helpers.go
package expr
import (
"fmt"
"strings"
)
func EvalString(ctx ExprContext, source string) (result any, err error) {
var tree *ast
r := strings.NewReader(source)
scanner := NewScanner(r, DefaultTranslations())
parser := NewParser(ctx)
if tree, err = parser.Parse(scanner); err == nil {
result, err = tree.eval(ctx, true)
}
return
}
type Arg struct {
Name string
Value any
}
func EvalStringA(source string, args ...Arg) (result any, err error) {
return EvalStringV(source, args)
}
func EvalStringV(source string, args []Arg) (result any, err error) {
ctx := NewSimpleFuncStore()
for _, arg := range args {
if isFunc(arg.Value) {
if f, ok := arg.Value.(FuncTemplate); ok {
functor := &simpleFunctor{f: f}
ctx.RegisterFunc(arg.Name, functor, 0, -1)
} else {
err = fmt.Errorf("invalid function specification: %q", arg.Name)
}
} else if integer, ok := anyInteger(arg.Value); ok {
ctx.SetVar(arg.Name, integer)
} else if float, ok := anyFloat(arg.Value); ok {
ctx.SetVar(arg.Name, float)
} else if _, ok := arg.Value.(string); ok {
ctx.SetVar(arg.Name, arg.Value)
} else if _, ok := arg.Value.(bool); ok {
ctx.SetVar(arg.Name, arg.Value)
} else {
err = fmt.Errorf("unsupported type %T specified for item %q", arg.Value, arg.Name)
}
}
if err == nil {
result, err = EvalString(ctx, source)
}
return
}
+81
View File
@@ -0,0 +1,81 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// helpers_test.go
package expr
import (
"fmt"
"testing"
)
func subtract(ctx ExprContext, name string, args []any) (result any, err error) {
if len(args) != 2 {
err = fmt.Errorf("%s(): requires exactly two arguments", name)
return
}
x, xok := args[0].(int64)
y, yok := args[1].(int64)
if xok && yok {
result = x - y
} else {
err = fmt.Errorf("expected integer (int64) arguments, got %T and %T values", x, y)
}
return
}
func TestEvalStringA(t *testing.T) {
source := `a + b * subtract(4,2)`
args := []Arg{
{"a", uint8(1)},
{"b", int8(2)},
{"subtract", FuncTemplate(subtract)},
// force coverage
{"a16", uint16(1)},
{"b16", int16(2)},
{"a32", uint32(1)},
{"b32", int32(2)},
{"a64", uint64(1)},
{"b64", int64(2)},
{"f32", float32(1.0)},
{"f64", float64(1.0)},
}
wantResult := int64(5)
gotResult, gotErr := EvalStringA(source, args...)
if value, ok := gotResult.(int64); ok && value != wantResult {
t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult)
t.Errorf("Error: %v", gotErr)
}
}
func TestEvalString(t *testing.T) {
ctx := NewSimpleVarStore()
ctx.SetVar("a", uint8(1))
ctx.SetVar("b", int8(2))
ctx.SetVar("f", 2.0)
// force coverage
ctx.SetVar("a16", uint16(1))
ctx.SetVar("b16", int16(2))
ctx.SetVar("a32", uint32(1))
ctx.SetVar("b32", int32(2))
ctx.SetVar("a64", uint64(1))
ctx.SetVar("b64", int64(2))
ctx.SetVar("f32", float32(1.0))
ctx.SetVar("f64", float64(1.0))
// force coverage
ctx.GetFuncInfo("dummy")
ctx.Call("dummy", []any{})
source := `a + b * f`
wantResult := float64(5)
gotResult, gotErr := EvalString(ctx, source)
if value, ok := gotResult.(float64); ok && value != wantResult {
t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult)
t.Errorf("Error: %v", gotErr)
}
}
+3
View File
@@ -0,0 +1,3 @@
First test
next commit
nr 3
+58
View File
@@ -0,0 +1,58 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// it-range.go
package expr
import "io"
type RangeIterator struct {
start int64
end int64
step int64
current int64
index int
}
func NewRangeIterator(start, end, step int64) *RangeIterator {
if step == 0 {
panic("Range step must be not zero")
}
if step < 0 && start < end {
panic("When the range's step is less than zero, start must be greater than end")
}
if step > 0 && start > end {
panic("When the range's step is greater than zero, start must be less than end")
}
return &RangeIterator{start: start, end: end, step: step, current: start, index: 0}
}
func (it *RangeIterator) Reset() {
it.index = 0
it.current = it.start
}
func (it *RangeIterator) Next() (item any, err error) {
if it.step > 0 {
if it.current < it.end {
item = it.current
it.current += it.step
it.index++
} else {
err = io.EOF
}
} else {
if it.current > it.end {
item = it.current
it.current += it.step
it.index++
} else {
err = io.EOF
}
}
return
}
func (it *RangeIterator) Index() int {
return it.index - 1
}
+40
View File
@@ -0,0 +1,40 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// 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 // operand-const.go
package expr package expr
@@ -5,8 +8,8 @@ package expr
func newBoolTerm(tk *Token) *term { func newBoolTerm(tk *Token) *term {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classConst, // class: classConst,
kind: kindBool, // kind: kindBool,
parent: nil, parent: nil,
children: nil, children: nil,
position: posLeaf, position: posLeaf,
@@ -19,8 +22,6 @@ func newBoolTerm(tk *Token) *term {
func newIntegerTerm(tk *Token) *term { func newIntegerTerm(tk *Token) *term {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classConst,
kind: kindInteger,
parent: nil, parent: nil,
children: nil, children: nil,
position: posLeaf, position: posLeaf,
@@ -33,8 +34,8 @@ func newIntegerTerm(tk *Token) *term {
func newFloatTerm(tk *Token) *term { func newFloatTerm(tk *Token) *term {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classConst, // class: classConst,
kind: kindFloat, // kind: kindFloat,
parent: nil, parent: nil,
children: nil, children: nil,
position: posLeaf, position: posLeaf,
@@ -47,8 +48,8 @@ func newFloatTerm(tk *Token) *term {
func newStringTerm(tk *Token) *term { func newStringTerm(tk *Token) *term {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classConst, // class: classConst,
kind: kindString, // kind: kindString,
parent: nil, parent: nil,
children: nil, children: nil,
position: posLeaf, position: posLeaf,
@@ -58,7 +59,7 @@ func newStringTerm(tk *Token) *term {
} }
// -------- eval func // -------- 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 v = self.tk.Value
return 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)
// }
+102 -8
View File
@@ -1,22 +1,30 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-func.go // operand-func.go
package expr package expr
// -------- function term import (
func newFuncTerm(tk *Token, args []*term) *term { "errors"
)
// -------- function call term
func newFuncCallTerm(tk *Token, args []*term) *term {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classVar, // class: classVar,
kind: kindUnknown, // kind: kindUnknown,
parent: nil, parent: nil,
children: args, children: args,
position: posLeaf, position: posLeaf,
priority: priValue, priority: priValue,
evalFunc: evalFunc, evalFunc: evalFuncCall,
} }
} }
// -------- eval func // -------- eval func call
func evalFunc(ctx exprContext, self *term) (v any, err error) { func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
ctx := parentCtx.Clone()
name, _ := self.tk.Value.(string) name, _ := self.tk.Value.(string)
params := make([]any, len(self.children)) params := make([]any, len(self.children))
for i, tree := range self.children { for i, tree := range self.children {
@@ -27,7 +35,93 @@ func evalFunc(ctx exprContext, self *term) (v any, err error) {
params[i] = param params[i] = param
} }
if err == nil { 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) {
arg := args[i]
if functor, ok := arg.(Functor); ok {
ctx.RegisterFunc(p, functor, 0, -1)
} else {
ctx.setVar(p, arg)
}
} else {
ctx.setVar(p, nil)
}
}
result, err = functor.expr.eval(ctx, false)
return
}
func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
bodySpec := self.value()
if expr, ok := bodySpec.(*ast); ok {
paramList := make([]string, 0, len(self.children))
for _, param := range self.children {
paramList = append(paramList, param.source())
// 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 return
} }
+66
View File
@@ -0,0 +1,66 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-list.go
package expr
// -------- list term
func newListTermA(args ...*term) *term {
return newListTerm(args)
}
func newListTerm(args []*term) *term {
return &term{
tk: *NewValueToken(0, 0, SymList, "[]", args),
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalList,
}
}
// -------- list func
func evalList(ctx ExprContext, self *term) (v any, err error) {
list, _ := self.value().([]*term)
items := make([]any, len(list))
for i, tree := range list {
var param any
if param, err = tree.compute(ctx); err != nil {
break
}
items[i] = param
}
if err == nil {
v = items
}
return
}
// // -------- list term
// func newListTerm(args []*term) *term {
// return &term{
// tk: *NewToken(0, 0, SymList, "[]"),
// parent: nil,
// children: args,
// position: posLeaf,
// priority: priValue,
// evalFunc: evalList,
// }
// }
// // -------- list func
// func evalList(ctx ExprContext, self *term) (v any, err error) {
// items := make([]any, len(self.children))
// for i, tree := range self.children {
// var param any
// if param, err = tree.compute(ctx); err != nil {
// break
// }
// items[i] = param
// }
// if err == nil {
// v = items
// }
// return
// }
+50
View File
@@ -0,0 +1,50 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-selector-case.go
package expr
import (
"fmt"
"strings"
)
// -------- selector case term
type selectorCase struct {
filterList *term
caseExpr Expr
}
func (sc *selectorCase) String() string {
var sb strings.Builder
if sc.filterList != nil {
sc.filterList.toString(&sb)
sb.WriteByte(' ')
}
sb.WriteByte('{')
sb.WriteString(sc.caseExpr.String())
sb.WriteByte('}')
return sb.String()
}
func newSelectorCaseTerm(row, col int, filterList *term, caseExpr Expr) *term {
tk := NewValueToken(row, col, SymSelectorCase, "", &selectorCase{filterList: filterList, caseExpr: caseExpr})
return &term{
tk: *tk,
parent: nil,
children: nil,
position: posLeaf,
priority: priValue,
evalFunc: evalSelectorCase,
}
}
// -------- eval selector case
func evalSelectorCase(ctx ExprContext, self *term) (v any, err error) {
var ok bool
if v, ok = self.value().(*selectorCase); !ok {
err = fmt.Errorf("selector-case expected, got %T", self.value())
}
return
}
+7 -4
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-var.go // operand-var.go
package expr package expr
@@ -7,8 +10,8 @@ import "fmt"
func newVarTerm(tk *Token) *term { func newVarTerm(tk *Token) *term {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classVar, // class: classVar,
kind: kindUnknown, // kind: kindUnknown,
parent: nil, parent: nil,
children: nil, children: nil,
position: posLeaf, position: posLeaf,
@@ -18,9 +21,9 @@ func newVarTerm(tk *Token) *term {
} }
// -------- eval func // -------- eval func
func evalVar(ctx exprContext, self *term) (v any, err error) { func evalVar(ctx ExprContext, self *term) (v any, err error) {
var exists bool 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) err = fmt.Errorf("undefined variable %q", self.tk.source)
} }
return 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 // operator-bool.go
package expr package expr
import "fmt"
//-------- NOT term //-------- NOT term
func newNotTerm(tk *Token) (inst *term) { func newNotTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classOperator,
kind: kindBool,
children: make([]*term, 0, 1), children: make([]*term, 0, 1),
position: posPrefix, position: posPrefix,
priority: priNot, 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 var rightValue any
if rightValue, err = self.evalPrefix(ctx); err != nil { 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) { func newAndTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classOperator, // class: classOperator,
kind: kindBool, // kind: kindBool,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priAnd, priority: priAnd,
@@ -44,7 +47,16 @@ func newAndTerm(tk *Token) (inst *term) {
} }
} }
func evalAnd(ctx exprContext, self *term) (v any, err error) { func evalAnd(ctx ExprContext, self *term) (v any, err error) {
if isEnabled(ctx, ControlBoolShortcut) {
v, err = evalAndWithShortcut(ctx, self)
} else {
v, err = evalAndWithoutShortcut(ctx, self)
}
return
}
func evalAndWithoutShortcut(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any var leftValue, rightValue any
var leftBool, rightBool bool var leftBool, rightBool bool
var lok, rok bool var lok, rok bool
@@ -64,13 +76,39 @@ func evalAnd(ctx exprContext, self *term) (v any, err error) {
return 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 //-------- OR term
func newOrTerm(tk *Token) (inst *term) { func newOrTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classOperator, // class: classOperator,
kind: kindBool, // kind: kindBool,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priOr, priority: priOr,
@@ -78,7 +116,16 @@ func newOrTerm(tk *Token) (inst *term) {
} }
} }
func evalOr(ctx exprContext, self *term) (v any, err error) { func evalOr(ctx ExprContext, self *term) (v any, err error) {
if isEnabled(ctx, ControlBoolShortcut) {
v, err = evalOrWithShortcut(ctx, self)
} else {
v, err = evalOrWithoutShortcut(ctx, self)
}
return
}
func evalOrWithoutShortcut(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any var leftValue, rightValue any
var leftBool, rightBool bool var leftBool, rightBool bool
var lok, rok bool var lok, rok bool
@@ -98,6 +145,32 @@ func evalOr(ctx exprContext, self *term) (v any, err error) {
return 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 // init
func init() { func init() {
registerTermConstructor(SymNot, newNotTerm) registerTermConstructor(SymNot, newNotTerm)
+57
View File
@@ -0,0 +1,57 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-length.go
package expr
//-------- builtin term
func newBuiltinTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priSign,
evalFunc: evalBuiltin,
}
}
func evalBuiltin(ctx ExprContext, self *term) (v any, err error) {
var rightValue any
if rightValue, err = self.evalPrefix(ctx); err != nil {
return
}
count := 0
if isList(rightValue) {
list, _ := rightValue.([]any)
for i, moduleSpec := range list {
if module, ok := moduleSpec.(string); ok {
if ImportInContext(ctx, module) {
count++
} else {
err = self.Errorf("unknown module %q", module)
break
}
} else {
err = self.Errorf("expected string at item nr %d, got %T", i+1, moduleSpec)
break
}
}
} else if isString(rightValue) {
module, _ := rightValue.(string)
count, err = ImportInContextByGlobPattern(ctx, module)
} else {
err = self.errIncompatibleType(rightValue)
}
if err == nil {
v = count
}
return
}
// init
func init() {
registerTermConstructor(SymKwBuiltin, newBuiltinTerm)
}
+27
View File
@@ -0,0 +1,27 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-but.go
package expr
//-------- but term
func newButTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priBut,
evalFunc: evalBut,
}
}
func evalBut(ctx ExprContext, self *term) (v any, err error) {
_, v, err = self.evalInfix(ctx)
return
}
// init
func init() {
registerTermConstructor(SymKwBut, newButTerm)
}
+93
View File
@@ -0,0 +1,93 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-coalesce.go
package expr
//-------- null coalesce term
func newNullCoalesceTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
// class: classOperator,
// kind: kindUnknown,
children: make([]*term, 0, 2),
position: posInfix,
priority: priCoalesce,
evalFunc: evalNullCoalesce,
}
}
func evalNullCoalesce(ctx ExprContext, self *term) (v any, err error) {
var rightValue any
if err = self.checkOperands(); err != nil {
return
}
leftTerm := self.children[0]
if leftTerm.tk.Sym != SymIdentifier {
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
return
}
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
v = leftValue
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
if _, ok := rightValue.(Functor); ok {
err = errCoalesceNoFunc(self.children[1])
} else {
v = rightValue
}
}
return
}
//-------- coalesce assign term
func newCoalesceAssignTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priCoalesce,
evalFunc: evalAssignCoalesce,
}
}
func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
var rightValue any
if err = self.checkOperands(); err != nil {
return
}
leftTerm := self.children[0]
if leftTerm.tk.Sym != SymIdentifier {
err = leftTerm.Errorf("left operand of %q must be a variable", self.tk.source)
return
}
if leftValue, exists := ctx.GetVar(leftTerm.source()); exists {
v = leftValue
} else if rightValue, err = self.children[1].compute(ctx); err == nil {
if _, 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)
}
+61
View File
@@ -0,0 +1,61 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-dot.go
package expr
// -------- dot term
func newDotTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priDot,
evalFunc: evalDot,
}
}
func evalDot(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
indexTerm := self.children[1]
if !isInteger(rightValue) {
err = indexTerm.Errorf("index expression must be integer, got %T", rightValue)
return
}
index64, _ := rightValue.(int64)
index := int(index64)
if isList(leftValue) {
list, _ := leftValue.([]any)
if index >= 0 && index < len(list) {
v = list[index]
} else if index >= -len(list) {
v = list[len(list)+index]
} else {
err = indexTerm.Errorf("index %v out of bounds", index)
}
} else if isString(leftValue) {
s, _ := leftValue.(string)
if index >= 0 && index < len(s) {
v = string(s[index])
} else if index >= -len(s) {
v = string(s[len(s)+index])
} else {
err = indexTerm.Errorf("index %v out of bounds", index)
}
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
// init
func init() {
registerTermConstructor(SymDot, newDotTerm)
}
+4 -3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-fact.go // operator-fact.go
package expr package expr
@@ -8,8 +11,6 @@ import "fmt"
func newFactTerm(tk *Token) (inst *term) { func newFactTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classOperator,
kind: kindInteger,
children: make([]*term, 0, 1), children: make([]*term, 0, 1),
position: posPostfix, position: posPostfix,
priority: priFact, 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 var leftValue any
if leftValue, err = self.evalPrefix(ctx); err != nil { if leftValue, err = self.evalPrefix(ctx); err != nil {
+65
View File
@@ -0,0 +1,65 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-insert.go
package expr
//-------- insert term
func newInsertTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priAssign,
evalFunc: evalInsert,
}
}
func newAppendTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 2),
position: posInfix,
priority: priAssign,
evalFunc: evalAppend,
}
}
func evalInsert(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if isList(rightValue) {
list, _ := rightValue.([]any)
v = append([]any{leftValue}, list...)
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
func evalAppend(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
return
}
if isList(leftValue) {
list, _ := leftValue.([]any)
v = append(list, rightValue)
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
return
}
// init
func init() {
registerTermConstructor(SymInsert, newInsertTerm)
registerTermConstructor(SymAppend, newAppendTerm)
}
+44
View File
@@ -0,0 +1,44 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-length.go
package expr
//-------- length term
func newLengthTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 1),
position: posPrefix,
priority: priSign,
evalFunc: evalLength,
}
}
func evalLength(ctx ExprContext, self *term) (v any, err error) {
var rightValue any
if rightValue, err = self.evalPrefix(ctx); err != nil {
return
}
if isList(rightValue) {
list, _ := rightValue.([]any)
v = len(list)
} else if isString(rightValue) {
s, _ := rightValue.(string)
v = len(s)
// } else {
// v = 1
// }
} else {
err = self.errIncompatibleType(rightValue)
}
return
}
// init
func init() {
registerTermConstructor(SymHash, newLengthTerm)
}
+78 -6
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-prod.go // operator-prod.go
package expr package expr
@@ -11,8 +14,8 @@ import (
func newMultiplyTerm(tk *Token) (inst *term) { func newMultiplyTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classOperator, // class: classOperator,
kind: kindUnknown, // kind: kindUnknown,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priProduct, 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 var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { 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) { func newDivideTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classOperator, // class: classOperator,
kind: kindUnknown, // kind: kindUnknown,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priProduct, 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 var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
@@ -88,8 +91,77 @@ func evalDivide(ctx exprContext, self *term) (v any, err error) {
return 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 // init
func init() { func init() {
registerTermConstructor(SymStar, newMultiplyTerm) registerTermConstructor(SymStar, newMultiplyTerm)
registerTermConstructor(SymSlash, newDivideTerm) 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 // operator-rel.go
package expr package expr
@@ -6,8 +9,8 @@ package expr
func newEqualTerm(tk *Token) (inst *term) { func newEqualTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classOperator, // class: classOperator,
kind: kindBool, // kind: kindBool,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priRelational, 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 var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { 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) { func newNotEqualTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classOperator, // class: classOperator,
kind: kindBool, // kind: kindBool,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priRelational, 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 { if v, err = evalEqual(ctx, self); err == nil {
b, _ := toBool(v) b, _ := toBool(v)
v = !b v = !b
@@ -92,8 +95,8 @@ func evalNotEqual(ctx exprContext, self *term) (v any, err error) {
func newLessTerm(tk *Token) (inst *term) { func newLessTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classOperator, // class: classOperator,
kind: kindBool, // kind: kindBool,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priRelational, 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 var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { 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) { func newLessEqualTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priRelational, 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 var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { 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) { func newGreaterTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classOperator, // class: classOperator,
kind: kindBool, // kind: kindBool,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priRelational, 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 { if v, err = evalLessEqual(ctx, self); err == nil {
b, _ := toBool(v) b, _ := toBool(v)
v = !b v = !b
@@ -192,8 +193,8 @@ func evalGreater(ctx exprContext, self *term) (v any, err error) {
func newGreaterEqualTerm(tk *Token) (inst *term) { func newGreaterEqualTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classOperator, // class: classOperator,
kind: kindBool, // kind: kindBool,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priRelational, 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 { if v, err = evalLess(ctx, self); err == nil {
b, _ := toBool(v) b, _ := toBool(v)
v = !b v = !b
+126
View File
@@ -0,0 +1,126 @@
// 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
// }
//-------- export all term
func newSelectorTerm(tk *Token) (inst *term) {
return &term{
tk: *tk,
children: make([]*term, 0, 3),
position: posInfix,
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 if filterList, ok := caseData.filterList.value().([]*term); ok {
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
}
caseListTerm := self.children[1]
caseList, _ := caseListTerm.value().([]*term)
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 // operator-sign.go
package expr package expr
@@ -6,8 +9,8 @@ package expr
func newPlusSignTerm(tk *Token) (inst *term) { func newPlusSignTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classOperator, // class: classOperator,
kind: kindUnknown, // kind: kindUnknown,
children: make([]*term, 0, 1), children: make([]*term, 0, 1),
position: posPrefix, position: posPrefix,
priority: priSign, priority: priSign,
@@ -18,8 +21,6 @@ func newPlusSignTerm(tk *Token) (inst *term) {
func newMinusSignTerm(tk *Token) (inst *term) { func newMinusSignTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 1), children: make([]*term, 0, 1),
position: posPrefix, position: posPrefix,
priority: priSign, 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 var rightValue any
if rightValue, err = self.evalPrefix(ctx); err != nil { if rightValue, err = self.evalPrefix(ctx); err != nil {
+33 -6
View File
@@ -1,8 +1,12 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operator-sum.go // operator-sum.go
package expr package expr
import ( import (
"fmt" "fmt"
"slices"
) )
//-------- plus term //-------- plus term
@@ -10,8 +14,6 @@ import (
func newPlusTerm(tk *Token) (inst *term) { func newPlusTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priSum, 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 var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { 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) rightInt, _ := rightValue.(int64)
v = leftInt + rightInt 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 { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
} }
@@ -47,8 +66,6 @@ func evalPlus(ctx exprContext, self *term) (v any, err error) {
func newMinusTerm(tk *Token) (inst *term) { func newMinusTerm(tk *Token) (inst *term) {
return &term{ return &term{
tk: *tk, tk: *tk,
class: classOperator,
kind: kindUnknown,
children: make([]*term, 0, 2), children: make([]*term, 0, 2),
position: posInfix, position: posInfix,
priority: priSum, priority: priSum,
@@ -56,7 +73,7 @@ func newMinusTerm(tk *Token) (inst *term) {
} }
} }
func evalMinus(ctx exprContext, self *term) (v any, err error) { func evalMinus(ctx ExprContext, self *term) (v any, err error) {
var leftValue, rightValue any var leftValue, rightValue any
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
@@ -71,6 +88,16 @@ func evalMinus(ctx exprContext, self *term) (v any, err error) {
rightInt, _ := rightValue.(int64) rightInt, _ := rightValue.(int64)
v = leftInt - rightInt 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 { } else {
err = self.errIncompatibleTypes(leftValue, rightValue) err = self.errIncompatibleTypes(leftValue, rightValue)
} }
+262 -42
View File
@@ -1,63 +1,47 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// parser.go // parser.go
package expr package expr
import ( import (
"errors"
"fmt" "fmt"
) )
//-------- parser //-------- parser
type parser struct { type parser struct {
ctx exprContext ctx ExprContext
} }
func NewParser(ctx exprContext) (p *parser) { func NewParser(ctx ExprContext) (p *parser) {
p = &parser{ p = &parser{
ctx: ctx, ctx: ctx,
} }
return p return p
} }
func (self *parser) parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, err error) { func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token) (tree *term, err error) {
tree = NewAst() // name, _ := tk.Value.(string)
firstToken := true // funcObj := self.ctx.GetFuncInfo(name)
for tk := scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); tk = scanner.Next() { // if funcObj == nil {
if tk.Sym == SymComment { // err = fmt.Errorf("unknown function %s()", name)
continue // return
} // }
// maxArgs := funcObj.MaxArgs()
//fmt.Println("Token:", tk) // if maxArgs < 0 {
if firstToken && (tk.Sym == SymMinus || tk.Sym == SymPlus) { // maxArgs = funcObj.MinArgs() + 10
if tk.Sym == SymMinus { // }
tk.Sym = SymChangeSign // args := make([]*term, 0, maxArgs)
} else { args := make([]*term, 0, 10)
tk.Sym = SymUnchangeSign
}
}
firstToken = false
if tk.Sym == SymOpenRound {
var subTree *ast
if subTree, err = self.parse(scanner, SymClosedRound); err == nil {
subTree.root.priority = priValue
tree.addTerm(subTree.root)
}
} else if tk.Sym == SymFunction {
name, _ := tk.Value.(string)
funcObj := self.ctx.GetFuncInfo(name)
if funcObj == nil {
err = fmt.Errorf("unknown function %q", name)
break
}
maxArgs := funcObj.MaxArgs()
if maxArgs < 0 {
maxArgs = funcObj.MinArgs() + 10
}
args := make([]*term, 0, maxArgs)
lastSym := SymUnknown lastSym := SymUnknown
for lastSym != SymClosedRound && lastSym != SymEos { for lastSym != SymClosedRound && lastSym != SymEos {
var subTree *ast 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) args = append(args, subTree.root)
}
} else { } else {
break break
} }
@@ -65,12 +49,248 @@ func (self *parser) parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, e
} }
if err == nil { if err == nil {
// TODO Check arguments // TODO Check arguments
t := newFuncTerm(tk, args) if lastSym != SymClosedRound {
tree.addTerm(t) err = errors.New("unterminate arguments list")
}
} else { } else {
err = tree.addToken(tk) tree = newFuncCallTerm(tk, args)
} }
} }
return 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 addSelectorCase(selectorTerm, caseTerm *term) {
if len(selectorTerm.children) < 2 {
caseListTerm := newListTermA(caseTerm)
selectorTerm.children = append(selectorTerm.children, caseListTerm)
} else {
caseListTerm := selectorTerm.children[1]
caseList, _ := caseListTerm.value().([]*term)
caseList = append(caseList, caseTerm)
caseListTerm.tk.Value = caseList
}
caseTerm.parent = selectorTerm
}
func (self *parser) parseSelector(scanner *scanner, tree *ast, allowVarRef bool) (selectorTerm *term, err error) {
var caseTerm *term
tk := scanner.makeToken(SymSelector, '?')
if selectorTerm, err = tree.addToken2(tk); err != nil {
return
}
if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, false); err == nil {
addSelectorCase(selectorTerm, caseTerm)
}
return
}
func (self *parser) parseItem(scanner *scanner, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
return self.parseGeneral(scanner, false, allowVarRef, termSymbols...)
}
func (self *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, err error) {
return self.parseGeneral(scanner, true, false, termSymbols...)
}
func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
var selectorTerm *term = nil
var currentTerm *term = nil
var tk *Token
tree = NewAst()
firstToken := true
lastSym := SymUnknown
for tk = scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); tk = scanner.Next() {
if tk.Sym == SymComment {
continue
}
if tk.Sym == SymSemiColon {
if allowForest {
tree.ToForest()
firstToken = true
continue
} else {
err = tk.Errorf(`unexpected token %q, expected ",", "]", or ")"`, tk.source)
break
}
}
//fmt.Println("Token:", tk)
if firstToken {
if tk.Sym == SymMinus {
tk.Sym = SymChangeSign
} else if tk.Sym == SymPlus {
tk.Sym = SymUnchangeSign
}
firstToken = false
}
switch tk.Sym {
case SymOpenRound:
var subTree *ast
if subTree, err = self.parseGeneral(scanner, false, allowVarRef, SymClosedRound); err == nil {
subTree.root.priority = priValue
err = tree.addTerm(subTree.root)
currentTerm = subTree.root
}
case SymFuncCall:
var funcCallTerm *term
if funcCallTerm, err = self.parseFuncCall(scanner, allowVarRef, tk); err == nil {
err = tree.addTerm(funcCallTerm)
currentTerm = funcCallTerm
}
case SymOpenSquare:
var listTerm *term
if listTerm, err = self.parseList(scanner, allowVarRef); err == nil {
err = tree.addTerm(listTerm)
currentTerm = listTerm
}
case 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, err = self.parseSelector(scanner, tree, allowVarRef); err == nil {
currentTerm = selectorTerm
}
case SymColon, SymDoubleColon:
var caseTerm *term
if selectorTerm == nil {
err = tk.Errorf("selector-case outside of a selector context")
} else if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
addSelectorCase(selectorTerm, caseTerm)
currentTerm = caseTerm
if tk.Sym == SymDoubleColon {
selectorTerm = nil
}
}
default:
currentTerm, err = tree.addToken2(tk)
}
if currentTerm != nil && currentTerm.tk.Sym != SymSelector && currentTerm.parent != nil && currentTerm.parent.tk.Sym != SymSelector {
selectorTerm = nil
}
lastSym = tk.Sym
}
if err == nil {
err = tk.Error()
}
return
}
func checkPrevSymbol(lastSym, wantedSym Symbol, tk *Token) (err error) {
if lastSym != wantedSym {
err = fmt.Errorf(`assign operator (%q) must be preceded by a variable`, tk.source)
}
return
}
+218 -116
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// parser_test.go // parser_test.go
package expr package expr
@@ -8,89 +11,6 @@ import (
"testing" "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) { func TestParser(t *testing.T) {
type inputType struct { type inputType struct {
source string source string
@@ -98,15 +18,7 @@ func TestParser(t *testing.T) {
wantErr error wantErr error
} }
ctx := newTestContext()
ctx.SetValue("var1", int64(123))
ctx.SetValue("var2", "abc")
inputs := []inputType{ 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}, /* 1 */ {`1+/*5*/2`, int64(3), nil},
/* 2 */ {`3 == 4`, false, nil}, /* 2 */ {`3 == 4`, false, nil},
/* 3 */ {`3 != 4`, true, nil}, /* 3 */ {`3 != 4`, true, nil},
@@ -140,18 +52,18 @@ func TestParser(t *testing.T) {
/* 31 */ {"(1+1)*5", int64(10), nil}, /* 31 */ {"(1+1)*5", int64(10), nil},
/* 32 */ {"200 / (1+1) - 1", int64(99), nil}, /* 32 */ {"200 / (1+1) - 1", int64(99), nil},
/* 33 */ {`add(1,2,3)`, int64(6), 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}, /* 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}, /* 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}, /* 38 */ {`1+(1+(1+(1)+1)+1)+1`, int64(7), nil},
/* 39 */ {`(((1)))`, int64(1), nil}, /* 39 */ {`(((1)))`, int64(1), nil},
/* 40 */ {`"uno_" + var2`, `uno_abc`, nil}, /* 40 */ {`"uno_" + var2`, `uno_abc`, nil},
/* 41 */ {`0 || 0.0 && "hello"`, false, 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 "+"`)}, /* 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(`prefix/postfix operator "+" do not support operand 'false' [bool]`)}, /* 43 */ {`+false`, nil, errors.New(`[1:2] prefix/postfix operator "+" do not support operand 'false' [bool]`)},
/* 44 */ {`false // very simple expression`, false, nil}, /* 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`)}, /* 46 */ {"", nil, errors.New(`empty expression`)},
/* 47 */ {"4!", int64(24), nil}, /* 47 */ {"4!", int64(24), nil},
/* 48 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)}, /* 48 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)},
@@ -166,50 +78,240 @@ func TestParser(t *testing.T) {
/* 57 */ {`"1.5" > "7"`, false, nil}, /* 57 */ {`"1.5" > "7"`, false, nil},
/* 58 */ {`"1.5" == "7"`, false, nil}, /* 58 */ {`"1.5" == "7"`, false, nil},
/* 59 */ {`"1.5" != "7"`, true, nil}, /* 59 */ {`"1.5" != "7"`, true, nil},
/* 60 */ {"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(`infix operator ">" requires two 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(`infix operator "<=" requires two 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(`infix operator ">=" requires two 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(`infix operator "!=" requires two 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(`infix operator "==" requires two 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(`infix operator "<" requires two 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(`infix operator ">" requires two 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(`infix operator "==" requires two 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(`infix operator "!=" requires two 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}, /* 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`)}, /* 72 */ {"4 / 0", nil, errors.New(`division by zero`)},
/* 73 */ {"4.0 / 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}, /* 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},
/* 137 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
} }
check_env_expr_path := 113
succeeded := 0
failed := 0
// inputs1 := []inputType{
// {`10 ? {"a"} :[true, 2+8] {"b"} :: {"c"}`, "b", nil},
// }
parser := NewParser(ctx)
for i, input := range inputs { for i, input := range inputs {
var expr *ast var expr Expr
var gotResult any var gotResult any
var gotErr error var gotErr error
if input.wantErr == nil { ctx := NewSimpleFuncStore()
t.Log(fmt.Sprintf("[+]Test nr %2d -- %q", i+1, input.source)) ctx.SetVar("var1", int64(123))
} else { ctx.SetVar("var2", "abc")
t.Log(fmt.Sprintf("[-]Test nr %2d -- %q", i+1, input.source)) ImportMathFuncs(ctx)
} ImportImportFuncs(ctx)
parser := NewParser(ctx)
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
r := strings.NewReader(input.source) r := strings.NewReader(input.source)
scanner := NewScanner(r, DefaultTranslations()) scanner := NewScanner(r, DefaultTranslations())
if expr, gotErr = parser.parse(scanner); gotErr == nil { good := true
gotResult, gotErr = expr.eval(ctx) if expr, gotErr = parser.Parse(scanner); gotErr == nil {
gotResult, gotErr = expr.Eval(ctx)
} }
if gotResult != input.wantResult { if gotResult != input.wantResult {
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, 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 gotErr != input.wantErr {
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) { 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) t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
good = false
} }
} }
if good {
succeeded++
} else {
failed++
if i+1 == check_env_expr_path {
t.Logf(`NOTICE: Test nr %d requires EXPR_PATH="."`, check_env_expr_path)
}
}
}
t.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed))
}
func TestListParser(t *testing.T) {
type inputType struct {
source string
wantResult any
wantErr error
}
// 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};
+117 -90
View File
@@ -1,9 +1,13 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// expr project scanner.go // expr project scanner.go
package expr package expr
import ( import (
"bufio" "bufio"
"errors" "errors"
"fmt"
"io" "io"
"strconv" "strconv"
"strings" "strings"
@@ -35,6 +39,7 @@ func DefaultTranslations() map[Symbol]Symbol {
SymKwAnd: SymAnd, SymKwAnd: SymAnd,
SymDoubleVertBar: SymOr, SymDoubleVertBar: SymOr,
SymKwOr: SymOr, SymKwOr: SymOr,
SymTilde: SymNot,
SymKwNot: SymNot, SymKwNot: SymNot,
SymLessGreater: SymNotEqual, SymLessGreater: SymNotEqual,
} }
@@ -139,12 +144,19 @@ func (self *scanner) fetchNextToken() (tk *Token) {
case ',': case ',':
tk = self.makeToken(SymComma, ch) tk = self.makeToken(SymComma, ch)
case ':': case ':':
if next, _ := self.peek(); next == ':' {
tk = self.moveOn(SymDoubleColon, ch, next)
} else {
tk = self.makeToken(SymColon, ch) tk = self.makeToken(SymColon, ch)
}
case ';': case ';':
tk = self.makeToken(SymSemiColon, ch) tk = self.makeToken(SymSemiColon, ch)
case '.': case '.':
if next, _ := self.peek(); next >= '0' && next <= '9' { //if next, _ := self.peek(); next >= '0' && next <= '9' {
tk = self.parseNumber(ch) // tk = self.parseNumber(ch)
//} else if next == '/' {
if next, _ := self.peek(); next == '/' {
tk = self.moveOn(SymDotSlash, ch, next)
} else { } else {
tk = self.makeToken(SymDot, ch) tk = self.makeToken(SymDot, ch)
} }
@@ -166,7 +178,13 @@ func (self *scanner) fetchNextToken() (tk *Token) {
tk = self.makeToken(SymExclamation, ch) tk = self.makeToken(SymExclamation, ch)
} }
case '?': 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) tk = self.makeToken(SymQuestion, ch)
}
case '&': case '&':
if next, _ := self.peek(); next == '&' { if next, _ := self.peek(); next == '&' {
tk = self.moveOn(SymDoubleAmpersand, ch, next) tk = self.moveOn(SymDoubleAmpersand, ch, next)
@@ -178,7 +196,19 @@ func (self *scanner) fetchNextToken() (tk *Token) {
case '#': case '#':
tk = self.makeToken(SymHash, ch) tk = self.makeToken(SymHash, ch)
case '@': 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) tk = self.makeToken(SymAt, ch)
}
case '_': case '_':
tk = self.makeToken(SymUndescore, ch) tk = self.makeToken(SymUndescore, ch)
case '=': case '=':
@@ -190,6 +220,8 @@ func (self *scanner) fetchNextToken() (tk *Token) {
case '<': case '<':
if next, _ := self.peek(); next == '=' { if next, _ := self.peek(); next == '=' {
tk = self.moveOn(SymLessOrEqual, ch, next) tk = self.moveOn(SymLessOrEqual, ch, next)
} else if next == '<' {
tk = self.moveOn(SymAppend, ch, next)
} else if next == '>' { } else if next == '>' {
tk = self.moveOn(SymLessGreater, ch, next) tk = self.moveOn(SymLessGreater, ch, next)
} else { } else {
@@ -198,6 +230,8 @@ func (self *scanner) fetchNextToken() (tk *Token) {
case '>': case '>':
if next, _ := self.peek(); next == '=' { if next, _ := self.peek(); next == '=' {
tk = self.moveOn(SymGreaterOrEqual, ch, next) tk = self.moveOn(SymGreaterOrEqual, ch, next)
} else if next == '>' {
tk = self.moveOn(SymInsert, ch, next)
} else { } else {
tk = self.makeToken(SymGreater, ch) tk = self.makeToken(SymGreater, ch)
} }
@@ -215,14 +249,20 @@ func (self *scanner) fetchNextToken() (tk *Token) {
tk = self.makeToken(SymOpenBrace, ch) tk = self.makeToken(SymOpenBrace, ch)
case '}': case '}':
tk = self.makeToken(SymClosedBrace, ch) tk = self.makeToken(SymClosedBrace, ch)
case '~':
tk = self.makeToken(SymTilde, ch)
case 0: case 0:
if escape { if escape {
tk = self.makeErrorToken(errors.New("incomplete escape sequence")) tk = self.makeErrorToken(errors.New("incomplete escape sequence"))
} }
escape = false escape = false
default: default:
if ch == '_' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') { if /*ch == '_' ||*/ (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') {
tk = self.fetchIdentifier(ch) 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' { } else if ch >= '0' && ch <= '9' {
tk = self.parseNumber(ch) tk = self.parseNumber(ch)
} }
@@ -241,17 +281,78 @@ func (self *scanner) sync(err error) error {
return err return err
} }
func isBinaryDigit(ch byte) bool {
return ch == '0' || ch == '1'
}
func isOctalDigit(ch byte) bool {
return ch >= '0' && ch <= '7'
}
func isDecimalDigit(ch byte) bool {
return ch >= '0' && ch <= '9'
}
func isHexDigit(ch byte) bool {
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
}
func (self *scanner) initBase(sb *strings.Builder, currentFirstCh byte) (firstCh byte, numBase int, digitFunc func(byte) bool, err error) {
var ch byte
var digitType string
firstCh = currentFirstCh
digitFunc = isDecimalDigit
numBase = 10
if ch, err = self.peek(); err == nil {
if ch == 'b' || ch == 'B' {
numBase = 2
digitType = "binary"
self.readChar()
digitFunc = isBinaryDigit
firstCh, err = self.readChar()
} else if ch == 'o' || ch == 'O' {
numBase = 8
digitType = "octal"
self.readChar()
digitFunc = isOctalDigit
firstCh, err = self.readChar()
} else if ch == 'x' || ch == 'X' {
numBase = 16
digitType = "hex"
self.readChar()
digitFunc = isHexDigit
firstCh, err = self.readChar()
}
if err == nil && !digitFunc(firstCh) {
if len(digitType) == 0 {
digitType = "decimal"
}
err = fmt.Errorf("expected %s digit, got '%c'", digitType, firstCh)
}
} else if err == io.EOF {
err = nil
}
return
}
func (self *scanner) parseNumber(firstCh byte) (tk *Token) { func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
var err error var err error
var ch byte var ch byte
var sym Symbol = SymInteger var sym Symbol = SymInteger
var value any
var sb strings.Builder var sb strings.Builder
var isDigit func(byte) bool = isDecimalDigit
var numBase = 10
for ch = firstCh; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() { if firstCh == '0' {
firstCh, numBase, isDigit, err = self.initBase(&sb, firstCh)
}
for ch = firstCh; err == nil && isDigit(ch); ch, err = self.readChar() {
sb.WriteByte(ch) sb.WriteByte(ch)
} }
if ch == '.' {
if numBase == 10 {
if err == nil && ch == '.' {
sym = SymFloat sym = SymFloat
sb.WriteByte(ch) sb.WriteByte(ch)
ch, err = self.readChar() ch, err = self.readChar()
@@ -261,7 +362,7 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
} }
} }
} }
if ch == 'e' || ch == 'E' { if err == nil && (ch == 'e' || ch == 'E') {
sym = SymFloat sym = SymFloat
sb.WriteByte(ch) sb.WriteByte(ch)
if ch, err = self.readChar(); err == nil { if ch, err = self.readChar(); err == nil {
@@ -273,30 +374,23 @@ func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() { for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
sb.WriteByte(ch) sb.WriteByte(ch)
} }
//err = self.sync(err)
} else { } else {
err = errors.New("expected integer exponent") err = errors.New("expected integer exponent")
} }
} }
// } else { }
// err = self.sync(err)
} }
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
tk = self.makeErrorToken(err) tk = self.makeErrorToken(err)
} else { } else {
var value any
err = self.sync(err) err = self.sync(err)
txt := sb.String() txt := sb.String()
if sym == SymFloat { if sym == SymFloat {
value, err = strconv.ParseFloat(txt, 64) value, err = strconv.ParseFloat(txt, 64)
} else if strings.HasPrefix(txt, "0x") {
value, err = strconv.ParseInt(txt, 16, 64)
} else if strings.HasPrefix(txt, "0o") {
value, err = strconv.ParseInt(txt, 8, 64)
} else if strings.HasPrefix(txt, "0b") {
value, err = strconv.ParseInt(txt, 2, 64)
} else { } else {
value, err = strconv.ParseInt(txt, 10, 64) value, err = strconv.ParseInt(txt, numBase, 64)
} }
if err == nil { if err == nil {
tk = self.makeValueToken(sym, txt, value) tk = self.makeValueToken(sym, txt, value)
@@ -329,7 +423,7 @@ func (self *scanner) fetchIdentifier(firstCh byte) (tk *Token) {
tk = self.makeValueToken(SymBool, txt, false) tk = self.makeValueToken(SymBool, txt, false)
} else if ch, _ := self.peek(); ch == '(' { } else if ch, _ := self.peek(); ch == '(' {
self.readChar() self.readChar()
tk = self.makeValueToken(SymFunction, txt+"(", uptxt) tk = self.makeValueToken(SymFuncCall, txt+"(", txt)
} else { } else {
tk = self.makeValueToken(SymIdentifier, txt, txt) tk = self.makeValueToken(SymIdentifier, txt, txt)
} }
@@ -359,73 +453,6 @@ func (self *scanner) fetchOnLineComment() *Token {
return self.fetchUntil(SymComment, true, '\n') 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) { func (self *scanner) fetchUntil(sym Symbol, allowEos bool, endings ...byte) (tk *Token) {
var err error var err error
var ch byte var ch byte
@@ -525,7 +552,7 @@ func (self *scanner) translate(sym Symbol) Symbol {
} }
func (self *scanner) moveOn(sym Symbol, chars ...byte) (tk *Token) { 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++ { for i := 1; i < len(chars); i++ {
self.readChar() self.readChar()
} }
@@ -533,17 +560,17 @@ func (self *scanner) moveOn(sym Symbol, chars ...byte) (tk *Token) {
} }
func (self *scanner) makeToken(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 return
} }
func (self *scanner) makeKeywordToken(sym Symbol, upperCaseKeyword string) (tk *Token) { 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 return
} }
func (self *scanner) makeValueToken(sym Symbol, source string, value any) (tk *Token) { 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 return
} }
+3
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// scanner_test.go // scanner_test.go
package expr package expr
+82
View File
@@ -0,0 +1,82 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// simple-func-store.go
package expr
import "fmt"
type SimpleFuncStore struct {
SimpleVarStore
funcStore map[string]*funcInfo
}
type funcInfo struct {
name string
minArgs int
maxArgs int
functor Functor
}
func (info *funcInfo) Name() string {
return info.name
}
func (info *funcInfo) MinArgs() int {
return info.minArgs
}
func (info *funcInfo) MaxArgs() int {
return info.maxArgs
}
func (info *funcInfo) Functor() Functor {
return info.functor
}
func NewSimpleFuncStore() *SimpleFuncStore {
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
}
+70
View File
@@ -0,0 +1,70 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// simple-var-store.go
package expr
import "fmt"
type SimpleVarStore struct {
varStore map[string]any
}
func NewSimpleVarStore() *SimpleVarStore {
return &SimpleVarStore{
varStore: make(map[string]any),
}
}
func (ctx *SimpleVarStore) Clone() (clone ExprContext) {
clone = &SimpleVarStore{
varStore: CloneMap(ctx.varStore),
}
return clone
}
func (ctx *SimpleVarStore) GetVar(varName string) (v any, exists bool) {
v, exists = ctx.varStore[varName]
return
}
func (ctx *SimpleVarStore) setVar(varName string, value any) {
ctx.varStore[varName] = value
}
func (ctx *SimpleVarStore) SetVar(varName string, value any) {
if allowedValue, ok := fromGenericAny(value); ok {
ctx.varStore[varName] = allowedValue
} else {
panic(fmt.Errorf("unsupported type %T of value %v", value, value))
}
}
func (ctx *SimpleVarStore) EnumVars(acceptor func(name string) (accept bool)) (varNames []string) {
varNames = make([]string, 0)
for name := range ctx.varStore {
if acceptor != nil {
if acceptor(name) {
varNames = append(varNames, name)
}
} else {
varNames = append(varNames, name)
}
}
return
}
func (ctx *SimpleVarStore) GetFuncInfo(name string) (f ExprFunc) {
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
}
+56 -31
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// Symbol.go // Symbol.go
package expr package expr
@@ -24,32 +27,40 @@ const (
SymColon // 16: ':' SymColon // 16: ':'
SymSemiColon // 17: ';' SymSemiColon // 17: ';'
SymDot // 18: '.' SymDot // 18: '.'
SymQuote // 19: '\'' SymDotSlash // 19: './'
SymDoubleQuote // 20: '"' SymQuote // 20: '\''
SymBackTick // 0: '`' SymDoubleQuote // 21: '"'
SymExclamation // 0: '!' SymBackTick // 22: '`'
SymQuestion // 0: '?' SymExclamation // 23: '!'
SymAmpersand // 0: '&&' SymQuestion // 24: '?'
SymDoubleAmpersand // 0: '&&' SymAmpersand // 25: '&&'
SymPercent // 0: '%' SymDoubleAmpersand // 26: '&&'
SymAt // 0: '@' SymPercent // 27: '%'
SymUndescore // 0: '_' SymAt // 28: '@'
SymEqual // 0: '=' SymUndescore // 29: '_'
SymDoubleEqual // 0: '==' SymEqual // 30: '='
SymLess // 0: '<' SymDoubleEqual // 31: '=='
SymLessOrEqual // 0: '<=' SymLess // 32: '<'
SymGreater // 0: '>' SymLessOrEqual // 33: '<='
SymGreaterOrEqual // 0: '>=' SymGreater // 34: '>'
SymLessGreater // 0: '<>' SymGreaterOrEqual // 35: '>='
SymNotEqual // 0: '!=' SymLessGreater // 36: '<>'
SymDollar // 0: '$' SymNotEqual // 37: '!='
SymHash // 0: '#' SymDollar // 38: '$'
SymOpenRound // 0: '(' SymHash // 39: '#'
SymClosedRound // 0: ')' SymOpenRound // 40: '('
SymOpenSquare // 0: '[' SymClosedRound // 41: ')'
SymClosedSquare // 0: ']' SymOpenSquare // 42: '['
SymOpenBrace // 0: '{' SymClosedSquare // 43: ']'
SymClosedBrace // 0: '}' SymOpenBrace // 44: '{'
SymClosedBrace // 45: '}'
SymTilde // 46: '~'
SymDoubleQuestion // 47: '??'
SymQuestionEqual // 48: '?='
SymDoubleAt // 49: '@@'
SymDoubleColon // 50: '::'
SymInsert // 51: '>>'
SymAppend // 52: '<<'
SymChangeSign SymChangeSign
SymUnchangeSign SymUnchangeSign
SymIdentifier SymIdentifier
@@ -57,17 +68,28 @@ const (
SymInteger SymInteger
SymFloat SymFloat
SymString SymString
SymKwAnd
SymKwNot
SymKwOr
SymOr SymOr
SymAnd SymAnd
SymNot SymNot
SymComment SymComment
SymFunction SymFuncCall
SymFuncDef
SymList
SymExpression
SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
// SymOpenComment // 0: '/*' // SymOpenComment // 0: '/*'
// SymClosedComment // 0: '*/' // SymClosedComment // 0: '*/'
// SymOneLineComment // 0: '//' // SymOneLineComment // 0: '//'
keywordBase
)
const (
SymKwAnd = keywordBase + iota
SymKwNot
SymKwOr
SymKwBut
SymKwFunc
SymKwBuiltin
) )
var keywords map[string]Symbol var keywords map[string]Symbol
@@ -76,7 +98,10 @@ func init() {
//keywords = make(map[string]Symbol) //keywords = make(map[string]Symbol)
keywords = map[string]Symbol{ keywords = map[string]Symbol{
"AND": SymKwAnd, "AND": SymKwAnd,
"OR": SymKwOr, "BUILTIN": SymKwBuiltin,
"BUT": SymKwBut,
"FUNC": SymKwFunc,
"NOT": SymKwNot, "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 // op-registry.go
package expr package expr
+46 -65
View File
@@ -1,44 +1,30 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// term.go // term.go
package expr package expr
import ( import (
"fmt"
"strings" "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 type termPriority uint32
const ( const (
priNone termPriority = iota priNone termPriority = iota
priBut
priAssign
priOr priOr
priAnd priAnd
priNot priNot
priRelational priRelational
priSum priSum
priProduct priProduct
priSelector
priSign priSign
priFact priFact
priCoalesce
priDot
priValue priValue
) )
@@ -49,27 +35,16 @@ const (
posInfix posInfix
posPrefix posPrefix
posPostfix posPostfix
posMultifix
) )
type evalFuncType func(ctx exprContext, self *term) (v any, err error) 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 term struct { type term struct {
tk Token tk Token
class termClass
kind termKind
parent *term parent *term
children []*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 priority termPriority // operator priority: higher value means higher priority
evalFunc evalFuncType evalFunc evalFuncType
} }
@@ -141,14 +116,6 @@ func (self *term) isLeaf() bool {
return self.position == posLeaf 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 { func (self *term) getPriority() termPriority {
return self.priority return self.priority
} }
@@ -160,21 +127,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 { func (self *term) source() string {
return self.tk.source 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 { 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 { } else {
v, err = self.evalFunc(ctx, self) v, err = self.evalFunc(ctx, self)
} }
@@ -182,37 +145,55 @@ func (self *term) compute(ctx exprContext) (v any, err error) {
} }
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error { func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
return fmt.Errorf( return self.tk.Errorf(
"left operand '%v' [%T] is not compatible with right operand '%v' [%T] with respect to operator %q", "left operand '%v' [%T] and right operand '%v' [%T] are not compatible with operator %q",
leftValue, leftValue, leftValue, leftValue,
rightValue, rightValue, rightValue, rightValue,
self.source()) self.source())
} }
func (self *term) errIncompatibleType(value any) error { func (self *term) errIncompatibleType(value any) error {
return fmt.Errorf( return self.tk.Errorf(
"prefix/postfix operator %q do not support operand '%v' [%T]", self.source(), value, value) "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) { func (self *term) checkOperands() (err error) {
switch self.position { switch self.position {
case posInfix: case posInfix:
if self.children == nil || len(self.children) != 2 || self.children[0] == nil || self.children[1] == nil { if self.children == nil || len(self.children) != 2 || self.anyChildrenNil() {
err = fmt.Errorf("infix operator %q requires two operands, got %d", self.source(), self.getChildrenCount()) err = self.tk.Errorf("infix operator %q requires two non-nil operands, got %d", self.source(), self.getChildrenCount())
} }
case posPrefix: case posPrefix:
if self.children == nil || len(self.children) != 1 || self.children[0] == nil { 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: case posPostfix:
if self.children == nil || len(self.children) != 1 || self.children[0] == nil { if self.children == nil || len(self.children) != 1 || self.anyChildrenNil() {
err = fmt.Errorf("postfix operator %q requires one operand", self.tk.String()) 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 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 err = self.checkOperands(); err == nil {
if leftValue, err = self.children[0].compute(ctx); err == nil { if leftValue, err = self.children[0].compute(ctx); err == nil {
rightValue, err = self.children[1].compute(ctx) rightValue, err = self.children[1].compute(ctx)
@@ -221,7 +202,7 @@ func (self *term) evalInfix(ctx exprContext) (leftValue, rightValue any, err err
return 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 { if err = self.checkOperands(); err == nil {
rightValue, err = self.children[0].compute(ctx) rightValue, err = self.children[0].compute(ctx)
} }
+11 -9
View File
@@ -1,40 +1,42 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// term_test.go // term_test.go
package expr package expr
import ( import (
"fmt"
"testing" "testing"
) )
func TestString(t *testing.T) { func TestString(t *testing.T) {
tk1 := NewValueToken(SymInteger, "100", 100) tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
tk2 := NewToken(SymPlus, "+") tk2 := NewToken(0, 0, SymPlus, "+")
tk3 := NewValueToken(SymInteger, "50", 500) tk3 := NewValueToken(0, 0, SymInteger, "50", 500)
tree := NewAst() tree := NewAst()
if gotErr := tree.addTokens(tk1, tk2, tk3); gotErr == nil { if gotErr := tree.addTokens(tk1, tk2, tk3); gotErr == nil {
fmt.Println("Tree:", tree) t.Log("Tree:", tree)
} else { } else {
t.Errorf("err: got <%v>, want <nil>", gotErr) t.Errorf("err: got <%v>, want <nil>", gotErr)
} }
} }
func TestGetRoom(t *testing.T) { func TestGetRoom(t *testing.T) {
tk1 := NewValueToken(SymInteger, "100", 100) tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
tree := NewAst() tree := NewAst()
if gotErr := tree.addTokens(tk1); gotErr == nil { if gotErr := tree.addTokens(tk1); gotErr == nil {
fmt.Println("Tree-root room:", tree.root.getRoom()) t.Log("Tree-root room:", tree.root.getRoom())
} else { } else {
t.Errorf("err: got <%v>, want <nil>", gotErr) t.Errorf("err: got <%v>, want <nil>", gotErr)
} }
} }
func TestGetChildrenCount(t *testing.T) { func TestGetChildrenCount(t *testing.T) {
tk1 := NewValueToken(SymInteger, "100", 100) tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
tree := NewAst() tree := NewAst()
if gotErr := tree.addTokens(tk1); gotErr == nil { if gotErr := tree.addTokens(tk1); gotErr == nil {
fmt.Println("Tree-root children count:", tree.root.getChildrenCount()) t.Log("Tree-root children count:", tree.root.getChildrenCount())
} else { } else {
t.Errorf("err: got <%v>, want <nil>", gotErr) t.Errorf("err: got <%v>, want <nil>", gotErr)
} }
+15
View File
@@ -0,0 +1,15 @@
/*
test-funcs.expr: example source file
*/
// double(x): returns 2*x
@double=func(x){2*x};
// Define variable 'a' wth value 5
@a=5;
// two(): returns 2
@two=func() {2};
// six(): returns 6
six=func() {6};
+35 -7
View File
@@ -1,3 +1,6 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// token.go // token.go
package expr package expr
@@ -8,6 +11,8 @@ import (
) )
type Token struct { type Token struct {
row int
col int
Sym Symbol Sym Symbol
source string source string
Value any Value any
@@ -22,24 +27,28 @@ func (tk *Token) DevString() string {
func (tk *Token) String() string { func (tk *Token) String() string {
if tk.Value != nil { if tk.Value != nil {
return fmt.Sprintf("%#v", tk.Value) if s, ok := tk.Value.(string); ok {
return fmt.Sprintf("%q", s)
} else {
return fmt.Sprintf("%v", tk.Value)
}
} }
return fmt.Sprintf("%s", tk.source) return fmt.Sprintf("%s", tk.source)
} }
func NewToken(sym Symbol, source string) *Token { func NewToken(row, col int, sym Symbol, source string) *Token {
return &Token{Sym: sym, source: source} return &Token{row: row, col: col, Sym: sym, source: source}
} }
func NewValueToken(sym Symbol, source string, value any) *Token { func NewValueToken(row, col int, sym Symbol, source string, value any) *Token {
return &Token{Sym: sym, source: source, Value: value} return &Token{row: row, col: col, Sym: sym, source: source, Value: value}
} }
func NewErrorToken(row, col int, err error) *Token { func NewErrorToken(row, col int, err error) *Token {
if err == io.EOF { 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 { func (tk *Token) IsEos() bool {
@@ -53,3 +62,22 @@ func (tk *Token) IsError() bool {
func (tk *Token) IsTerm(termSymbols []Symbol) bool { func (tk *Token) IsTerm(termSymbols []Symbol) bool {
return tk.IsEos() || tk.IsError() || (termSymbols != nil && slices.Index(termSymbols, tk.Sym) >= 0) return tk.IsEos() || tk.IsError() || (termSymbols != nil && slices.Index(termSymbols, tk.Sym) >= 0)
} }
func (self *Token) Errorf(template string, args ...any) (err error) {
err = fmt.Errorf(fmt.Sprintf("[%d:%d] ", self.row, self.col)+template, args...)
return
}
func (self *Token) Error() (err error) {
if self.Sym == SymError {
if msg, ok := self.Value.(error); ok {
err = fmt.Errorf("[%d:%d] %v", self.row, self.col, msg)
}
}
return
}
func (self *Token) Errors(msg string) (err error) {
err = fmt.Errorf("[%d:%d] %v", self.row, self.col, msg)
return
}
+28 -5
View File
@@ -1,15 +1,38 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// token_test.go // token_test.go
package expr package expr
import ( import (
"fmt"
"testing" "testing"
) )
func TestDevString(t *testing.T) { func TestDevString(t *testing.T) {
tk1 := NewValueToken(SymInteger, "100", 100) type inputType struct {
tk2 := NewToken(SymPlus, "+") source string
sym Symbol
value any
wantResult string
}
fmt.Println("Token '100':", tk1.DevString()) inputs := []inputType{
fmt.Println("Token '+':", tk2.DevString()) /* 1 */ {"100", SymInteger, 100, `[55]"100"{100}`},
/* 2 */ {"+", SymPlus, nil, `[6]"+"{}`},
}
for i, input := range inputs {
var tk *Token
if input.value == nil {
tk = NewToken(0, 0, input.sym, input.source)
} else {
tk = NewValueToken(0, 0, input.sym, input.source, input.value)
}
t.Logf("Test nr %2d: %q --> %q", i+1, input.source, input.wantResult)
if s := tk.DevString(); s != input.wantResult {
t.Errorf("wrong token from symbol '+': expected %q, got %q", input.wantResult, s)
}
}
} }
+82
View File
@@ -1,6 +1,11 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// utils.go // utils.go
package expr package expr
import "reflect"
func isString(v any) (ok bool) { func isString(v any) (ok bool) {
_, ok = v.(string) _, ok = v.(string)
return ok return ok
@@ -16,6 +21,11 @@ func isFloat(v any) (ok bool) {
return ok return ok
} }
func isList(v any) (ok bool) {
_, ok = v.([]any)
return ok
}
func isNumber(v any) (ok bool) { func isNumber(v any) (ok bool) {
return isFloat(v) || isInteger(v) return isFloat(v) || isInteger(v)
} }
@@ -49,3 +59,75 @@ func toBool(v any) (b bool, ok bool) {
} }
return 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)
}