Compare commits

...

14 Commits

Author SHA1 Message Date
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
19 changed files with 148 additions and 43 deletions
+47 -8
View File
@@ -68,7 +68,7 @@ import (
func main() { func main() {
ctx := expr.NewSimpleVarStore() ctx := expr.NewSimpleVarStore()
ctx.SetVar("var", int64(4)) ctx.SetVar("var", 4)
source := `(3-1)*(10/5) == var` source := `(3-1)*(10/5) == var`
@@ -101,7 +101,7 @@ import (
func main() { func main() {
ctx := expr.NewSimpleVarStore() ctx := expr.NewSimpleVarStore()
ctx.SetVar("var", int64(4)) ctx.SetVar("var", 4)
source := `(3-1)*(10/5) == var` source := `(3-1)*(10/5) == var`
@@ -121,14 +121,13 @@ package main
import ( import (
"fmt" "fmt"
"git.portale-stac.it/go-pkg/expr" "git.portale-stac.it/go-pkg/expr"
) )
func main() { func main() {
source := `(3-1)*(10/5) == var` source := `(3-1)*(10/5) == var`
if result, err := expr.EvalStringA(source, expr.Arg{"var", int64(4)}); err == nil { if result, err := expr.EvalStringA(source, expr.Arg{"var", 4}); err == nil {
fmt.Printf("%q -> %v [%T]\n", source, result, result) fmt.Printf("%q -> %v [%T]\n", source, result, result)
} else { } else {
fmt.Println("Error calculating the expression:", err) fmt.Println("Error calculating the expression:", err)
@@ -136,6 +135,9 @@ func main() {
} }
---- ----
=== Concepts and terminology
#TODO#
=== Data types === Data types
_Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists. _Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists.
@@ -237,10 +239,39 @@ Currently, boolean operations are evaluated using _short cut evaluation_. This m
==== ====
==== List ==== List
#TODO: List operations# _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 === Variables
#TODO: 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 === Other operations
@@ -326,12 +357,15 @@ The table below shows all supported operators by decreasing priorities.
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ "and" _boolean_ -> _boolean_ .2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ "and" _boolean_ -> _boolean_
| [blue]`&&` | _Infix_ | _and_ | _boolean_ "&&" _boolean_ -> _boolean_ | [blue]`&&` | _Infix_ | _and_ | _boolean_ "&&" _boolean_ -> _boolean_
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ "or" _boolean_ -> _boolean_ .2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ "or" _boolean_ -> _boolean_
| [blue]`||` | _Infix_ | _or_ | _boolean_ "||" _boolean_ -> _boolean_ | [blue]`\|\|` | _Infix_ | _or_ | _boolean_ "\|\|" _boolean_ -> _boolean_
.1+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_ .1+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_ .1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
|=== |===
=== Functions === 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 ==== Function calls
#TODO: function calls operations# #TODO: function calls operations#
@@ -339,8 +373,13 @@ The table below shows all supported operators by decreasing priorities.
==== Function definitions ==== Function definitions
#TODO: function definitions operations# #TODO: function definitions operations#
==== Builtins === Builtins
#TODO: 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.
+2 -1
View File
@@ -12,6 +12,7 @@ import (
type Expr interface { type Expr interface {
Eval(ctx ExprContext) (result any, err error) Eval(ctx ExprContext) (result any, err error)
eval(ctx ExprContext, preset bool) (result any, err error) eval(ctx ExprContext, preset bool) (result any, err error)
String() string
} }
//-------- ast //-------- ast
@@ -118,7 +119,7 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
if self.forest != nil { if self.forest != nil {
for _, root := range self.forest { for _, root := range self.forest {
if result, err = root.compute(ctx); err == nil { if result, err = root.compute(ctx); err == nil {
ctx.SetVar(control_last_result, result) ctx.setVar(ControlLastResult, result)
} else { } else {
//err = fmt.Errorf("error in expression nr %d: %v", i+1, err) //err = fmt.Errorf("error in expression nr %d: %v", i+1, err)
break break
+1
View File
@@ -33,6 +33,7 @@ type ExprContext interface {
Clone() ExprContext Clone() ExprContext
GetVar(varName string) (value any, exists bool) GetVar(varName string) (value any, exists bool)
SetVar(varName string, value any) SetVar(varName string, value any)
setVar(varName string, value any)
EnumVars(func(name string) (accept bool)) (varNames []string) EnumVars(func(name string) (accept bool)) (varNames []string)
EnumFuncs(func(name string) (accept bool)) (funcNames []string) EnumFuncs(func(name string) (accept bool)) (funcNames []string)
GetFuncInfo(name string) ExprFunc GetFuncInfo(name string) ExprFunc
+5 -5
View File
@@ -5,9 +5,9 @@ import "strings"
// Preset control variables // Preset control variables
const ( const (
control_last_result = "_last" ControlLastResult = "last"
control_bool_shortcut = "_bool_shortcut" ControlBoolShortcut = "_bool_shortcut"
control_import_path = "_import_path" ControlImportPath = "_import_path"
) )
// Other control variables // Other control variables
@@ -21,8 +21,8 @@ const (
) )
func initDefaultVars(ctx ExprContext) { func initDefaultVars(ctx ExprContext) {
ctx.SetVar(control_bool_shortcut, true) ctx.SetVar(ControlBoolShortcut, true)
ctx.SetVar(control_import_path, init_import_path) ctx.SetVar(ControlImportPath, init_import_path)
} }
func enable(ctx ExprContext, name string) { func enable(ctx ExprContext, name string) {
+2 -2
View File
@@ -51,7 +51,7 @@ func addEnvImportDirs(dirList []string) []string {
} }
func addPresetImportDirs(ctx ExprContext, dirList []string) []string { func addPresetImportDirs(ctx ExprContext, dirList []string) []string {
if dirSpec, exists := getControlString(ctx, control_import_path); exists { if dirSpec, exists := getControlString(ctx, ControlImportPath); exists {
dirs := strings.Split(dirSpec, ":") dirs := strings.Split(dirSpec, ":")
if dirList == nil { if dirList == nil {
dirList = dirs dirList = dirs
@@ -133,7 +133,7 @@ func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (resu
return return
} }
func importImportFunc(ctx ExprContext) { func ImportImportFunc(ctx ExprContext) {
ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1) ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1)
ctx.RegisterFunc("include", &simpleFunctor{f: includeFunc}, 1, -1) ctx.RegisterFunc("include", &simpleFunctor{f: includeFunc}, 1, -1)
} }
+1 -1
View File
@@ -101,7 +101,7 @@ func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
return return
} }
func importMathFuncs(ctx ExprContext) { func ImportMathFuncs(ctx ExprContext) {
ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1) ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1) ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
} }
-2
View File
@@ -45,7 +45,6 @@ func TestEvalStringA(t *testing.T) {
t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult) t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult)
t.Errorf("Error: %v", gotErr) t.Errorf("Error: %v", gotErr)
} }
fmt.Println("Hello World!")
} }
func TestEvalString(t *testing.T) { func TestEvalString(t *testing.T) {
@@ -76,5 +75,4 @@ func TestEvalString(t *testing.T) {
t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult) t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult)
t.Errorf("Error: %v", gotErr) t.Errorf("Error: %v", gotErr)
} }
fmt.Println("Hello World!")
} }
+3 -3
View File
@@ -57,7 +57,7 @@ func exportVar(ctx ExprContext, name string, value any) {
if name[0] == '@' { if name[0] == '@' {
name = name[1:] name = name[1:]
} }
ctx.SetVar(name, value) ctx.setVar(name, value)
} }
func exportFunc(ctx ExprContext, name string, info ExprFunc) { func exportFunc(ctx ExprContext, name string, info ExprFunc) {
@@ -89,9 +89,9 @@ type funcDefFunctor struct {
func (functor *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) { func (functor *funcDefFunctor) Invoke(ctx ExprContext, name string, args []any) (result any, err error) {
for i, p := range functor.params { for i, p := range functor.params {
if i < len(args) { if i < len(args) {
ctx.SetVar(p, args[i]) ctx.setVar(p, args[i])
} else { } else {
ctx.SetVar(p, nil) ctx.setVar(p, nil)
} }
} }
result, err = functor.expr.eval(ctx, false) result, err = functor.expr.eval(ctx, false)
+16 -1
View File
@@ -4,7 +4,10 @@
// operand-selector-case.go // operand-selector-case.go
package expr package expr
import "fmt" import (
"fmt"
"strings"
)
// -------- selector case term // -------- selector case term
@@ -13,6 +16,18 @@ type selectorCase struct {
caseExpr Expr 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 { func newSelectorCaseTerm(row, col int, filterList *term, caseExpr Expr) *term {
tk := NewValueToken(row, col, SymSelectorCase, "", &selectorCase{filterList: filterList, caseExpr: caseExpr}) tk := NewValueToken(row, col, SymSelectorCase, "", &selectorCase{filterList: filterList, caseExpr: caseExpr})
return &term{ return &term{
+1 -1
View File
@@ -31,7 +31,7 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
if functor, ok := v.(Functor); ok { if functor, ok := v.(Functor); ok {
ctx.RegisterFunc(leftTerm.source(), functor, 0, -1) ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
} else { } else {
ctx.SetVar(leftTerm.tk.source, v) ctx.setVar(leftTerm.tk.source, v)
} }
} }
return return
+2 -2
View File
@@ -48,7 +48,7 @@ func newAndTerm(tk *Token) (inst *term) {
} }
func evalAnd(ctx ExprContext, self *term) (v any, err error) { func evalAnd(ctx ExprContext, self *term) (v any, err error) {
if isEnabled(ctx, control_bool_shortcut) { if isEnabled(ctx, ControlBoolShortcut) {
v, err = evalAndWithShortcut(ctx, self) v, err = evalAndWithShortcut(ctx, self)
} else { } else {
v, err = evalAndWithoutShortcut(ctx, self) v, err = evalAndWithoutShortcut(ctx, self)
@@ -117,7 +117,7 @@ func newOrTerm(tk *Token) (inst *term) {
} }
func evalOr(ctx ExprContext, self *term) (v any, err error) { func evalOr(ctx ExprContext, self *term) (v any, err error) {
if isEnabled(ctx, control_bool_shortcut) { if isEnabled(ctx, ControlBoolShortcut) {
v, err = evalOrWithShortcut(ctx, self) v, err = evalOrWithShortcut(ctx, self)
} else { } else {
v, err = evalOrWithoutShortcut(ctx, self) v, err = evalOrWithoutShortcut(ctx, self)
+1 -1
View File
@@ -75,7 +75,7 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
err = errCoalesceNoFunc(self.children[1]) err = errCoalesceNoFunc(self.children[1])
} else { } else {
v = rightValue v = rightValue
ctx.SetVar(leftTerm.source(), rightValue) ctx.setVar(leftTerm.source(), rightValue)
} }
} }
return return
+1 -1
View File
@@ -268,7 +268,7 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
currentTerm, err = tree.addToken2(tk) currentTerm, err = tree.addToken2(tk)
} }
if currentTerm != nil && currentTerm.parent != nil && currentTerm.parent.tk.Sym != SymSelector { if currentTerm != nil && currentTerm.tk.Sym != SymSelector && currentTerm.parent != nil && currentTerm.parent.tk.Sym != SymSelector {
selectorTerm = nil selectorTerm = nil
} }
+4 -4
View File
@@ -173,8 +173,8 @@ func TestParser(t *testing.T) {
ctx := NewSimpleFuncStore() ctx := NewSimpleFuncStore()
ctx.SetVar("var1", int64(123)) ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc") ctx.SetVar("var2", "abc")
importMathFuncs(ctx) ImportMathFuncs(ctx)
importImportFunc(ctx) ImportImportFunc(ctx)
parser := NewParser(ctx) parser := NewParser(ctx)
logTest(t, i+1, input.source, input.wantResult, input.wantErr) logTest(t, i+1, input.source, input.wantResult, input.wantErr)
@@ -204,7 +204,7 @@ func TestParser(t *testing.T) {
} else { } else {
failed++ failed++
if i+1 == check_env_expr_path { if i+1 == check_env_expr_path {
t.Logf(`NOTICE: Test nr %d requires EXPR_PATH environment variable with value "."`, check_env_expr_path) t.Logf(`NOTICE: Test nr %d requires EXPR_PATH="."`, check_env_expr_path)
} }
} }
} }
@@ -249,7 +249,7 @@ func TestListParser(t *testing.T) {
ctx := NewSimpleFuncStore() ctx := NewSimpleFuncStore()
ctx.SetVar("var1", int64(123)) ctx.SetVar("var1", int64(123))
ctx.SetVar("var2", "abc") ctx.SetVar("var2", "abc")
importMathFuncs(ctx) ImportMathFuncs(ctx)
parser := NewParser(ctx) parser := NewParser(ctx)
logTest(t, i+1, input.source, input.wantResult, input.wantErr) logTest(t, i+1, input.source, input.wantResult, input.wantErr)
+11 -1
View File
@@ -1,6 +1,8 @@
// simple-var-store.go // simple-var-store.go
package expr package expr
import "fmt"
type SimpleVarStore struct { type SimpleVarStore struct {
varStore map[string]any varStore map[string]any
} }
@@ -23,10 +25,18 @@ func (ctx *SimpleVarStore) GetVar(varName string) (v any, exists bool) {
return return
} }
func (ctx *SimpleVarStore) SetVar(varName string, value any) { func (ctx *SimpleVarStore) setVar(varName string, value any) {
ctx.varStore[varName] = value 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) { func (ctx *SimpleVarStore) EnumVars(acceptor func(name string) (accept bool)) (varNames []string) {
varNames = make([]string, 0) varNames = make([]string, 0)
for name := range ctx.varStore { for name := range ctx.varStore {
+3 -4
View File
@@ -5,7 +5,6 @@
package expr package expr
import ( import (
"fmt"
"testing" "testing"
) )
@@ -16,7 +15,7 @@ func TestString(t *testing.T) {
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)
} }
@@ -27,7 +26,7 @@ func TestGetRoom(t *testing.T) {
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)
} }
@@ -37,7 +36,7 @@ func TestGetChildrenCount(t *testing.T) {
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)
} }
+5 -1
View File
@@ -27,7 +27,11 @@ 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)
} }
+25 -5
View File
@@ -5,14 +5,34 @@
package expr package expr
import ( import (
"fmt"
"testing" "testing"
) )
func TestDevString(t *testing.T) { func TestDevString(t *testing.T) {
tk1 := NewValueToken(0, 0, SymInteger, "100", 100) type inputType struct {
tk2 := NewToken(0, 0, 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)
}
}
} }
+18
View File
@@ -73,6 +73,8 @@ func anyInteger(v any) (i int64, ok bool) {
i = int64(intval) i = int64(intval)
case uint16: case uint16:
i = int64(intval) i = int64(intval)
case uint64:
i = int64(intval)
case uint32: case uint32:
i = int64(intval) i = int64(intval)
case int8: case int8:
@@ -89,6 +91,22 @@ func anyInteger(v any) (i int64, ok bool) {
return 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) { func anyFloat(v any) (float float64, ok bool) {
ok = true ok = true
switch floatval := v.(type) { switch floatval := v.(type) {