Compare commits

...

6 Commits

11 changed files with 145 additions and 52 deletions
+84 -20
View File
@@ -63,35 +63,37 @@ package main
import (
"fmt"
"strings"
"git.portale-stac.it/go-pkg/expr"
)
func main() {
ctx := expr.NewSimpleVarStore()
ctx.SetValue("var", int64(4))
ctx.SetVar("var", 4)
source := `(3-1)*(10/5) == var`
r := strings.NewReader(source)
scanner := expr.NewScanner(r, DefaultTranslations())
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)
}
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)
}
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"
@@ -99,18 +101,43 @@ import (
func main() {
ctx := expr.NewSimpleVarStore()
ctx.SetValue("var", int64(4))
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)
}
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)
}
}
----
=== Concepts and terminology
#TODO#
=== Data types
_Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists.
@@ -212,10 +239,39 @@ Currently, boolean operations are evaluated using _short cut evaluation_. This m
====
==== 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
#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
@@ -301,12 +357,15 @@ The table below shows all supported operators by decreasing priorities.
.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_
| [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#
@@ -314,8 +373,13 @@ The table below shows all supported operators by decreasing priorities.
==== Function definitions
#TODO: function definitions operations#
==== Builtins
=== 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.
+1 -1
View File
@@ -118,7 +118,7 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
if self.forest != nil {
for _, root := range self.forest {
if result, err = root.compute(ctx); err == nil {
ctx.SetVar(control_last_result, result)
ctx.setVar(control_last_result, result)
} else {
//err = fmt.Errorf("error in expression nr %d: %v", i+1, err)
break
+1
View File
@@ -33,6 +33,7 @@ type ExprContext interface {
Clone() ExprContext
GetVar(varName string) (value any, exists bool)
SetVar(varName string, value any)
setVar(varName string, value any)
EnumVars(func(name string) (accept bool)) (varNames []string)
EnumFuncs(func(name string) (accept bool)) (funcNames []string)
GetFuncInfo(name string) ExprFunc
+6 -6
View File
@@ -21,23 +21,23 @@ const (
)
func initDefaultVars(ctx ExprContext) {
ctx.SetVar(control_bool_shortcut, true)
ctx.SetVar(control_import_path, init_import_path)
ctx.setVar(control_bool_shortcut, true)
ctx.setVar(control_import_path, init_import_path)
}
func enable(ctx ExprContext, name string) {
if strings.HasPrefix(name, "_") {
ctx.SetVar(name, true)
ctx.setVar(name, true)
} else {
ctx.SetVar("_"+name, true)
ctx.setVar("_"+name, true)
}
}
func disable(ctx ExprContext, name string) {
if strings.HasPrefix(name, "_") {
ctx.SetVar(name, false)
ctx.setVar(name, false)
} else {
ctx.SetVar("_"+name, false)
ctx.setVar("_"+name, false)
}
}
+18 -18
View File
@@ -19,35 +19,35 @@ func EvalString(ctx ExprContext, source string) (result any, err error) {
return
}
type EvalArg struct {
name string
value any
type Arg struct {
Name string
Value any
}
func EvalStringA(source string, args ...EvalArg) (result any, err error) {
func EvalStringA(source string, args ...Arg) (result any, err error) {
return EvalStringV(source, args)
}
func EvalStringV(source string, args []EvalArg) (result any, err error) {
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 {
if isFunc(arg.Value) {
if f, ok := arg.Value.(FuncTemplate); ok {
functor := &simpleFunctor{f: f}
ctx.RegisterFunc(arg.name, functor, 0, -1)
ctx.RegisterFunc(arg.Name, functor, 0, -1)
} else {
err = fmt.Errorf("invalid function specification: %q", arg.name)
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 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)
err = fmt.Errorf("unsupported type %T specified for item %q", arg.Value, arg.Name)
}
}
+1 -1
View File
@@ -24,7 +24,7 @@ func subtract(ctx ExprContext, name string, args []any) (result any, err error)
func TestEvalStringA(t *testing.T) {
source := `a + b * subtract(4,2)`
args := []EvalArg{
args := []Arg{
{"a", uint8(1)},
{"b", int8(2)},
{"subtract", FuncTemplate(subtract)},
+3 -3
View File
@@ -57,7 +57,7 @@ func exportVar(ctx ExprContext, name string, value any) {
if name[0] == '@' {
name = name[1:]
}
ctx.SetVar(name, value)
ctx.setVar(name, value)
}
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) {
for i, p := range functor.params {
if i < len(args) {
ctx.SetVar(p, args[i])
ctx.setVar(p, args[i])
} else {
ctx.SetVar(p, nil)
ctx.setVar(p, nil)
}
}
result, err = functor.expr.eval(ctx, false)
+1 -1
View File
@@ -31,7 +31,7 @@ func evalAssign(ctx ExprContext, self *term) (v any, err error) {
if functor, ok := v.(Functor); ok {
ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
} else {
ctx.SetVar(leftTerm.tk.source, v)
ctx.setVar(leftTerm.tk.source, v)
}
}
return
+1 -1
View File
@@ -75,7 +75,7 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
err = errCoalesceNoFunc(self.children[1])
} else {
v = rightValue
ctx.SetVar(leftTerm.source(), rightValue)
ctx.setVar(leftTerm.source(), rightValue)
}
}
return
+11 -1
View File
@@ -1,6 +1,8 @@
// simple-var-store.go
package expr
import "fmt"
type SimpleVarStore struct {
varStore map[string]any
}
@@ -23,10 +25,18 @@ func (ctx *SimpleVarStore) GetVar(varName string) (v any, exists bool) {
return
}
func (ctx *SimpleVarStore) SetVar(varName string, value any) {
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 {
+18
View File
@@ -73,6 +73,8 @@ func anyInteger(v any) (i int64, ok bool) {
i = int64(intval)
case uint16:
i = int64(intval)
case uint64:
i = int64(intval)
case uint32:
i = int64(intval)
case int8:
@@ -89,6 +91,22 @@ func anyInteger(v any) (i int64, ok bool) {
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) {