Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 723976b37e | |||
| 361b84f31f | |||
| 70892aa980 | |||
| 10eec286fa | |||
| 894b1884eb | |||
| d2bab5fd9e | |||
| f94f369547 | |||
| 107ec4958f | |||
| a22047e84e | |||
| 80b7d5b988 | |||
| 2ab896bbac | |||
| 62e16219f7 | |||
| 750c660331 | |||
| 7a88449cd1 | |||
| b14dc2f1ee | |||
| d354102c6a | |||
| 761ec868e6 | |||
| 7941c2dfec | |||
| ebb2811ed3 | |||
| 75c0c0f681 | |||
| 268a968548 | |||
| 323308d86f | |||
| b28d6a8f02 | |||
| ab82bcf1ef | |||
| a628bfac39 | |||
| d1122da566 | |||
| 6ae5ca34ed | |||
| 730b59e6d3 | |||
| f198ba47e1 | |||
| 943ef3327e | |||
| 475ef3c80a | |||
| 3c0307524b | |||
| c27e487fc3 | |||
| ed973c9b7b | |||
| 15bbfacd47 | |||
| 04f934ab04 | |||
| 591b4ffc19 | |||
| fe9ab9ebd2 | |||
| 7198749063 | |||
| b76481bbf2 | |||
| 4f05e5c90a | |||
| 8ad25afdc4 | |||
| 54bc759f70 | |||
| b6887af77a | |||
| 353d495c50 | |||
| f45b2c0a88 | |||
| 624e3ac0f2 | |||
| 35fcbd2bce | |||
| 2150181303 | |||
| d643e24a1b | |||
| 43e631f2e8 | |||
| f1afbf9b49 | |||
| ed6af6603a | |||
| 51e740d243 | |||
| 53dacd5332 | |||
| e297f2a1d3 | |||
| 70cdb9367e | |||
| efd9af9030 | |||
| b67d896415 | |||
| f03ae555fc | |||
| f36ae33acc | |||
| b9ea96f649 | |||
| 838ab9fd7e | |||
| ad6b4b872f | |||
| c75e6485e0 | |||
| 7b80f7f03b | |||
| 7f9fd570b2 | |||
| b17d2250a4 | |||
| dda10749d0 | |||
| 07ca84170e | |||
| 55e136e9bc | |||
| e493c40c7b | |||
| 95605232ab | |||
| 8f396a35de | |||
| bd323efedf | |||
| a9b143d012 | |||
| 024ff42be0 | |||
| 8ab2c28343 |
+40
-214
@@ -1,5 +1,5 @@
|
||||
= Expr
|
||||
Expressions calculator
|
||||
= README
|
||||
README about the Expressions calculator
|
||||
:authors: Celestino Amoroso
|
||||
:docinfo: shared
|
||||
:encoding: utf-8
|
||||
@@ -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,223 +101,47 @@ 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)
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
=== Data types
|
||||
_Expr_ supports numerical, string, relational, boolean expressions, and mixed-type lists.
|
||||
Here is another equivalent version.
|
||||
|
||||
==== 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_.
|
||||
====
|
||||
package main
|
||||
|
||||
==== List
|
||||
#TODO: List operations#
|
||||
import (
|
||||
"fmt"
|
||||
"git.portale-stac.it/go-pkg/expr"
|
||||
)
|
||||
|
||||
=== Variables
|
||||
#TODO: variables#
|
||||
func main() {
|
||||
source := `(3-1)*(10/5) == var`
|
||||
|
||||
=== 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
|
||||
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)
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
==== [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.
|
||||
== Context of evaluation
|
||||
Unless helpers functions like _expr.EvalStringA()_ are used, a _context_ is required to compute an expession.
|
||||
|
||||
[blue]`but` is very similar to [blue]`;`. The only difference is that [blue]`;` can't be used inside parenthesis [blue]`;(` and [blue]`)`.
|
||||
|
||||
==== Assignment operator [blue]`=`
|
||||
The assignment operator [blue]`=` is used to define variable in the evaluation context or to change their value (see _ExprContext_).
|
||||
The value on the left side of [blue]`=` must be an identifier. The value on the right side can be any expression and it becomes the result of the assignment operation.
|
||||
|
||||
.Example
|
||||
[source,go]
|
||||
----
|
||||
a=15+1 // returns 16
|
||||
----
|
||||
|
||||
==== Selector operator [blue]`? : ::`
|
||||
The _selector operator_ is very similar to the _switch/case/default_ statement available in many programming languages.
|
||||
|
||||
.Syntax
|
||||
[source,bnf]
|
||||
----
|
||||
<selector-operator> ::= <expression> "?" <selector-case> { ":" <selector-case> } ["::" <case-value>]
|
||||
<selector-case> ::= [<list>] <case-value>
|
||||
<case-value> ::= "{" <multi-expr> "}"
|
||||
<multi-expr> ::= <expr> {";" <expr>}
|
||||
----
|
||||
|
||||
.Example
|
||||
[source,go]
|
||||
----
|
||||
1 ? {"a"} : {"b"} // returns "b"
|
||||
10 ? {"a"} : {"b"} :: {"c"} // returns "c"
|
||||
10 ? {"a"} :[true, 2+8] {"b"} :: {"c"} // returns "b"
|
||||
10 ? {"a"} :[true, 2+8] {"b"} ::[10] {"c"} // error: "... case list in default clause"
|
||||
10 ? {"a"} :[10] {x="b" but x} :: {"c"} // returns "b"
|
||||
10 ? {"a"} :[10] {x="b"; x} :: {"c"} // returns "b"
|
||||
10 ? {"a"} : {"b"} // error: "... no case catches the value (10) of the selection expression
|
||||
----
|
||||
|
||||
=== Priorities of operators
|
||||
The table below shows all supported operators by decreasing priorities.
|
||||
|
||||
.Operators priorities
|
||||
[cols="^2,^2,^2,^5,<5"]
|
||||
|===
|
||||
| Priority | Operators | Position | Operation | Operands and results
|
||||
|
||||
1+|*FACT*| [blue]`!` | _Postfix_ | _Factorial_| _integer_ "!" -> _integer_
|
||||
1+|*SIGN*| [blue]`+`, [blue]`-` | _Prefix_ | _Change-sign_| ("+"\|"-") _number_ -> _number_
|
||||
.5+|*PROD*| [blue]`*` | _Infix_ | _Product_ | _number_ "*" _number_ -> _number_
|
||||
| [blue]`*` | _Infix_ | _String-repeat_ | _string_ "*" _integer_ -> _string_
|
||||
| [blue]`/` | _Infix_ | _Division_ | _number_ "/" _number_ -> _number_
|
||||
| [blue]`./` | _Infix_ | _Float-division_ | __number__ "./" _number_ -> _float_
|
||||
| [blue]`%` | _Infix_ | _Integer-remainder_ | _integer_ "%" _integer_ -> _integer_
|
||||
.5+|*SUM*| [blue]`+` | _Infix_ | _Sum_ | _number_ "+" _number_ -> _number_
|
||||
| [blue]`+` | _Infix_ | _String-concat_ | (_string_\|_number_) "+" (_string_\|_number_) -> _string_
|
||||
| [blue]`+` | _Infix_ | _List-join_ | _list_ "+" _list_ -> _list_
|
||||
| [blue]`-` | _Infix_ | _Subtraction_ | _number_ "-" _number_ -> _number_
|
||||
| [blue]`-` | _Infix_ | _List-difference_ | _list_ "-" _list_ -> _list_
|
||||
.6+|*RELATION*| [blue]`<` | _Infix_ | _less_ | _comparable_ "<" _comparable_ -> _boolean_
|
||||
| [blue]`\<=` | _Infix_ | _less-equal_ | _comparable_ "\<=" _comparable_ -> _boolean_
|
||||
| [blue]`>` | _Infix_ | _greater_ | _comparable_ ">" _comparable_ -> _boolean_
|
||||
| [blue]`>=` | _Infix_ | _greater-equal_ | _comparable_ ">=" _comparable_ -> _boolean_
|
||||
| [blue]`==` | _Infix_ | _equal_ | _comparable_ "==" _comparable_ -> _boolean_
|
||||
| [blue]`!=` | _Infix_ | _not-equal_ | _comparable_ "!=" _comparable_ -> _boolean_
|
||||
.1+|*NOT*| [blue]`not` | _Prefix_ | _not_ | "not" _boolean_ -> _boolean_
|
||||
.2+|*AND*| [blue]`and` | _Infix_ | _and_ | _boolean_ "and" _boolean_ -> _boolean_
|
||||
| [blue]`&&` | _Infix_ | _and_ | _boolean_ "&&" _boolean_ -> _boolean_
|
||||
.2+|*OR*| [blue]`or` | _Infix_ | _or_ | _boolean_ "or" _boolean_ -> _boolean_
|
||||
| [blue]`||` | _Infix_ | _or_ | _boolean_ "||" _boolean_ -> _boolean_
|
||||
.1+|*ASSIGN*| [blue]`=` | _Infix_ | _assignment_ | _identifier_ "=" _any_ -> _any_
|
||||
.1+|*BUT*| [blue]`but` | _Infix_ | _but_ | _any_ "but" _any_ -> _any_
|
||||
|===
|
||||
|
||||
=== Functions
|
||||
|
||||
==== Function calls
|
||||
#TODO: function calls operations#
|
||||
|
||||
==== Function definitions
|
||||
#TODO: function definitions operations#
|
||||
|
||||
==== Builtins
|
||||
#TODO: builtins#
|
||||
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#
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Expr interface {
|
||||
Eval(ctx ExprContext) (result any, err error)
|
||||
eval(ctx ExprContext, preset bool) (result any, err error)
|
||||
String() string
|
||||
}
|
||||
|
||||
//-------- ast
|
||||
@@ -83,8 +83,9 @@ func (self *ast) insert(tree, node *term) (root *term, err error) {
|
||||
if tree.isComplete() {
|
||||
var subRoot *term
|
||||
last := tree.removeLastChild()
|
||||
subRoot, err = self.insert(last, node)
|
||||
subRoot.setParent(tree)
|
||||
if subRoot, err = self.insert(last, node); err == nil {
|
||||
subRoot.setParent(tree)
|
||||
}
|
||||
} else {
|
||||
node.setParent(tree)
|
||||
}
|
||||
@@ -118,7 +119,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(ControlLastResult, result)
|
||||
} else {
|
||||
//err = fmt.Errorf("error in expression nr %d: %v", i+1, err)
|
||||
break
|
||||
@@ -128,8 +129,8 @@ func (self *ast) eval(ctx ExprContext, preset bool) (result any, err error) {
|
||||
if err == nil {
|
||||
result, err = self.root.compute(ctx)
|
||||
}
|
||||
} else {
|
||||
err = errors.New("empty expression")
|
||||
// } else {
|
||||
// err = errors.New("empty expression")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// context-helpers.go
|
||||
package expr
|
||||
|
||||
func cloneContext(sourceCtx ExprContext) (clonedCtx ExprContext) {
|
||||
if sourceCtx != nil {
|
||||
clonedCtx = sourceCtx.Clone()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func exportVar(ctx ExprContext, name string, value any) {
|
||||
if name[0] == '@' {
|
||||
name = name[1:]
|
||||
}
|
||||
ctx.setVar(name, value)
|
||||
}
|
||||
|
||||
func exportFunc(ctx ExprContext, name string, info ExprFunc) {
|
||||
if name[0] == '@' {
|
||||
name = name[1:]
|
||||
}
|
||||
ctx.RegisterFunc(name, info.Functor(), info.MinArgs(), info.MaxArgs())
|
||||
}
|
||||
|
||||
func exportObjects(destCtx, sourceCtx ExprContext) {
|
||||
exportAll := isEnabled(sourceCtx, control_export_all)
|
||||
// Export variables
|
||||
for _, refName := range sourceCtx.EnumVars(func(name string) bool { return exportAll || name[0] == '@' }) {
|
||||
refValue, _ := sourceCtx.GetVar(refName)
|
||||
exportVar(destCtx, refName, refValue)
|
||||
}
|
||||
// Export functions
|
||||
for _, refName := range sourceCtx.EnumFuncs(func(name string) bool { return exportAll || name[0] == '@' }) {
|
||||
if info, _ := sourceCtx.GetFuncInfo(refName); info != nil {
|
||||
exportFunc(destCtx, refName, info)
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -33,9 +33,10 @@ 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
|
||||
GetFuncInfo(name string) (item ExprFunc, exists bool)
|
||||
Call(name string, args []any) (result any, err error)
|
||||
RegisterFunc(name string, f Functor, minArgs, maxArgs int)
|
||||
}
|
||||
|
||||
+9
-6
@@ -1,13 +1,16 @@
|
||||
// preset.go
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// control.go
|
||||
package expr
|
||||
|
||||
import "strings"
|
||||
|
||||
// Preset control variables
|
||||
const (
|
||||
control_last_result = "_last"
|
||||
control_bool_shortcut = "_bool_shortcut"
|
||||
control_import_path = "_import_path"
|
||||
ControlLastResult = "last"
|
||||
ControlBoolShortcut = "_bool_shortcut"
|
||||
ControlImportPath = "_import_path"
|
||||
)
|
||||
|
||||
// Other control variables
|
||||
@@ -21,8 +24,8 @@ const (
|
||||
)
|
||||
|
||||
func initDefaultVars(ctx ExprContext) {
|
||||
ctx.SetVar(control_bool_shortcut, true)
|
||||
ctx.SetVar(control_import_path, init_import_path)
|
||||
ctx.SetVar(ControlBoolShortcut, true)
|
||||
ctx.SetVar(ControlImportPath, init_import_path)
|
||||
}
|
||||
|
||||
func enable(ctx ExprContext, name string) {
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// data-cursors.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
initName = "init"
|
||||
nextName = "next"
|
||||
currentName = "current"
|
||||
)
|
||||
|
||||
type dataCursor struct {
|
||||
ds map[any]*term
|
||||
ctx ExprContext
|
||||
index int
|
||||
resource any
|
||||
nextFunc Functor
|
||||
currentFunc Functor
|
||||
}
|
||||
|
||||
func newDataCursor(ctx ExprContext) (dc *dataCursor) {
|
||||
dc = &dataCursor{
|
||||
index: -1,
|
||||
ctx: ctx.Clone(),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (dc *dataCursor) String() string {
|
||||
return "$(...)"
|
||||
}
|
||||
|
||||
func (dc *dataCursor) Current() (item any, err error) { // must return io.EOF at the last item
|
||||
ctx := cloneContext(dc.ctx)
|
||||
if item, err = dc.currentFunc.Invoke(ctx, currentName, []any{}); err == nil && item == nil {
|
||||
err = io.EOF
|
||||
}
|
||||
exportObjects(dc.ctx, ctx)
|
||||
return
|
||||
}
|
||||
|
||||
func (dc *dataCursor) Next() (item any, err error) { // must return io.EOF after the last item
|
||||
ctx := cloneContext(dc.ctx)
|
||||
if item, err = dc.nextFunc.Invoke(ctx, nextName, []any{}); err == nil {
|
||||
if item == nil {
|
||||
err = io.EOF
|
||||
} else {
|
||||
dc.index++
|
||||
}
|
||||
}
|
||||
exportObjects(dc.ctx, ctx)
|
||||
return
|
||||
}
|
||||
|
||||
func (dc *dataCursor) Index() int {
|
||||
return dc.index
|
||||
}
|
||||
+278
@@ -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.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// expr_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExpr(t *testing.T) {
|
||||
type inputType struct {
|
||||
source string
|
||||
wantResult any
|
||||
wantErr error
|
||||
}
|
||||
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`0?{}`, nil, nil},
|
||||
/* 2 */ {`fact=func(n){(n)?{1}::{n*fact(n-1)}}; fact(5)`, int64(120), nil},
|
||||
/* 3 */ {`f=openFile("test-file.txt"); line=readFile(f); closeFile(f); line`, "uno", nil},
|
||||
/* 4 */ {`mynot=func(v){int(v)?{true}::{false}}; mynot(0)`, true, nil},
|
||||
}
|
||||
|
||||
succeeded := 0
|
||||
failed := 0
|
||||
|
||||
inputs1 := []inputType{
|
||||
/* 1 */ {`
|
||||
ds={
|
||||
"init":func(end){@end=end; @current=0 but true},
|
||||
"current":func(){current},
|
||||
"next":func(){
|
||||
((next=current+1) <= end) ? [true] {@current=next but current} :: {nil}
|
||||
}
|
||||
};
|
||||
it=$(ds,3);
|
||||
it++;
|
||||
it++
|
||||
`, int64(1), nil},
|
||||
}
|
||||
|
||||
for i, input := range inputs1 {
|
||||
var expr Expr
|
||||
var gotResult any
|
||||
var gotErr error
|
||||
|
||||
ctx := NewSimpleFuncStore()
|
||||
// ImportMathFuncs(ctx)
|
||||
// ImportImportFunc(ctx)
|
||||
ImportOsFuncs(ctx)
|
||||
parser := NewParser(ctx)
|
||||
|
||||
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
|
||||
|
||||
r := strings.NewReader(input.source)
|
||||
scanner := NewScanner(r, DefaultTranslations())
|
||||
|
||||
good := true
|
||||
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
|
||||
gotResult, gotErr = expr.Eval(ctx)
|
||||
}
|
||||
|
||||
if gotResult != input.wantResult {
|
||||
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
||||
good = false
|
||||
}
|
||||
|
||||
if gotErr != input.wantErr {
|
||||
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
|
||||
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
|
||||
good = false
|
||||
}
|
||||
}
|
||||
|
||||
if good {
|
||||
succeeded++
|
||||
} else {
|
||||
failed++
|
||||
}
|
||||
}
|
||||
t.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed))
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// func-builtins.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func isNilFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
if len(args) == 1 {
|
||||
result = args[0] == nil
|
||||
} else {
|
||||
err = errOneParam(name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func intFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
if len(args) == 1 {
|
||||
switch v := args[0].(type) {
|
||||
case int64:
|
||||
result = v
|
||||
case float64:
|
||||
result = int64(math.Trunc(v))
|
||||
case bool:
|
||||
if v {
|
||||
result = int64(1)
|
||||
} else {
|
||||
result = int64(0)
|
||||
}
|
||||
case string:
|
||||
var i int
|
||||
if i, err = strconv.Atoi(v); err == nil {
|
||||
result = int64(i)
|
||||
}
|
||||
default:
|
||||
err = errCantConvert(name, v, "int")
|
||||
}
|
||||
} else {
|
||||
err = errOneParam(name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func iteratorFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func ImportBuiltinsFuncs(ctx ExprContext) {
|
||||
ctx.RegisterFunc("isNil", &simpleFunctor{f: isNilFunc}, 1, -1)
|
||||
ctx.RegisterFunc("int", &simpleFunctor{f: intFunc}, 1, -1)
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerImport("builtins", ImportBuiltinsFuncs)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// func-common.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func errOneParam(funcName string) error {
|
||||
return fmt.Errorf("%s() requires exactly one param", funcName)
|
||||
}
|
||||
|
||||
func errCantConvert(funcName string, value any, kind string) error {
|
||||
return fmt.Errorf("%s() can't convert %T to %s", funcName, value, kind)
|
||||
}
|
||||
+9
-2
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// func-import.go
|
||||
package expr
|
||||
|
||||
@@ -51,7 +54,7 @@ func addEnvImportDirs(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, ":")
|
||||
if dirList == nil {
|
||||
dirList = dirs
|
||||
@@ -133,7 +136,11 @@ func doImport(ctx ExprContext, name string, dirList []string, it Iterator) (resu
|
||||
return
|
||||
}
|
||||
|
||||
func importImportFunc(ctx ExprContext) {
|
||||
func ImportImportFuncs(ctx ExprContext) {
|
||||
ctx.RegisterFunc("import", &simpleFunctor{f: importFunc}, 1, -1)
|
||||
ctx.RegisterFunc("include", &simpleFunctor{f: includeFunc}, 1, -1)
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerImport("import", ImportImportFuncs)
|
||||
}
|
||||
|
||||
+19
-7
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// funcs-math.go
|
||||
package expr
|
||||
|
||||
@@ -20,14 +23,19 @@ func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
|
||||
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 {
|
||||
if subIter, ok := v.(Iterator); ok {
|
||||
if v, err = doAdd(ctx, name, subIter); err != nil {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
|
||||
break
|
||||
}
|
||||
if array, ok := v.([]any); ok {
|
||||
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !sumAsFloat && isFloat(v) {
|
||||
@@ -101,7 +109,11 @@ func mulFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func importMathFuncs(ctx ExprContext) {
|
||||
func ImportMathFuncs(ctx ExprContext) {
|
||||
ctx.RegisterFunc("add", &simpleFunctor{f: addFunc}, 0, -1)
|
||||
ctx.RegisterFunc("mul", &simpleFunctor{f: mulFunc}, 0, -1)
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerImport("math.arith", ImportMathFuncs)
|
||||
}
|
||||
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// func-os.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type osHandle interface {
|
||||
getFile() *os.File
|
||||
}
|
||||
|
||||
type osWriter struct {
|
||||
fh *os.File
|
||||
writer *bufio.Writer
|
||||
}
|
||||
|
||||
func (h *osWriter) getFile() *os.File {
|
||||
return h.fh
|
||||
}
|
||||
|
||||
type osReader struct {
|
||||
fh *os.File
|
||||
reader *bufio.Reader
|
||||
}
|
||||
|
||||
func (h *osReader) getFile() *os.File {
|
||||
return h.fh
|
||||
}
|
||||
|
||||
func createFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var filePath string
|
||||
if len(args) > 0 {
|
||||
filePath, _ = args[0].(string)
|
||||
}
|
||||
|
||||
if len(filePath) > 0 {
|
||||
var fh *os.File
|
||||
if fh, err = os.Create(filePath); err == nil {
|
||||
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("%s(): missing the file path", name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func openFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var filePath string
|
||||
if len(args) > 0 {
|
||||
filePath, _ = args[0].(string)
|
||||
}
|
||||
|
||||
if len(filePath) > 0 {
|
||||
var fh *os.File
|
||||
if fh, err = os.Open(filePath); err == nil {
|
||||
result = &osReader{fh: fh, reader: bufio.NewReader(fh)}
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("%s(): missing the file path", name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var filePath string
|
||||
if len(args) > 0 {
|
||||
filePath, _ = args[0].(string)
|
||||
}
|
||||
|
||||
if len(filePath) > 0 {
|
||||
var fh *os.File
|
||||
if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil {
|
||||
result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("%s(): missing the file path", name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var handle osHandle
|
||||
|
||||
if len(args) > 0 {
|
||||
handle, _ = args[0].(osHandle)
|
||||
}
|
||||
|
||||
if handle != nil {
|
||||
if fh := handle.getFile(); fh != nil {
|
||||
if w, ok := handle.(*osWriter); ok {
|
||||
err = w.writer.Flush()
|
||||
}
|
||||
if err == nil {
|
||||
err = fh.Close()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("%s(): invalid file handle", name)
|
||||
}
|
||||
result = err == nil
|
||||
return
|
||||
}
|
||||
|
||||
func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var handle osHandle
|
||||
|
||||
if len(args) > 0 {
|
||||
handle, _ = args[0].(osHandle)
|
||||
}
|
||||
|
||||
if handle != nil {
|
||||
if fh := handle.getFile(); fh != nil {
|
||||
if w, ok := handle.(*osWriter); ok {
|
||||
result, err = fmt.Fprint(w.writer, args[1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
|
||||
var handle osHandle
|
||||
|
||||
if len(args) > 0 {
|
||||
handle, _ = args[0].(osHandle)
|
||||
}
|
||||
|
||||
if handle != nil {
|
||||
if fh := handle.getFile(); fh != nil {
|
||||
if r, ok := handle.(*osReader); ok {
|
||||
var limit byte = '\n'
|
||||
var v string
|
||||
if len(args) > 1 {
|
||||
if s, ok := args[1].(string); ok && len(s) > 0 {
|
||||
limit = s[0]
|
||||
}
|
||||
}
|
||||
if v, err = r.reader.ReadString(limit); err == nil || err == io.EOF {
|
||||
if len(v) > 0 && v[len(v)-1] == limit {
|
||||
result = v[0 : len(v)-1]
|
||||
} else {
|
||||
result = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ImportOsFuncs(ctx ExprContext) {
|
||||
ctx.RegisterFunc("openFile", &simpleFunctor{f: openFileFunc}, 1, 1)
|
||||
ctx.RegisterFunc("appendFile", &simpleFunctor{f: appendFileFunc}, 1, 1)
|
||||
ctx.RegisterFunc("createFile", &simpleFunctor{f: createFileFunc}, 1, 1)
|
||||
ctx.RegisterFunc("writeFile", &simpleFunctor{f: writeFileFunc}, 1, -1)
|
||||
ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2)
|
||||
ctx.RegisterFunc("closeFile", &simpleFunctor{f: closeFileFunc}, 1, 1)
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerImport("os", ImportOsFuncs)
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// funcs_test.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFuncs(t *testing.T) {
|
||||
type inputType struct {
|
||||
source string
|
||||
wantResult any
|
||||
wantErr error
|
||||
}
|
||||
|
||||
inputs := []inputType{
|
||||
/* 1 */ {`isNil(nil)`, true, nil},
|
||||
/* 2 */ {`v=nil; isNil(v)`, true, nil},
|
||||
/* 3 */ {`v=5; isNil(v)`, false, nil},
|
||||
/* 4 */ {`int(true)`, int64(1), nil},
|
||||
/* 5 */ {`int(false)`, int64(0), nil},
|
||||
/* 6 */ {`int(3.1)`, int64(3), nil},
|
||||
/* 7 */ {`int(3.9)`, int64(3), nil},
|
||||
/* 8 */ {`int("432")`, int64(432), nil},
|
||||
/* 9 */ {`int("1.5")`, nil, errors.New(`strconv.Atoi: parsing "1.5": invalid syntax`)},
|
||||
/* 10 */ {`int("432", 4)`, nil, errors.New(`int() requires exactly one param`)},
|
||||
/* 11 */ {`int(nil)`, nil, errors.New(`int() can't convert <nil> to int`)},
|
||||
}
|
||||
|
||||
succeeded := 0
|
||||
failed := 0
|
||||
|
||||
// inputs1 := []inputType{
|
||||
// /* 1 */ {`0?{}`, nil, nil},
|
||||
// }
|
||||
|
||||
for i, input := range inputs {
|
||||
var expr Expr
|
||||
var gotResult any
|
||||
var gotErr error
|
||||
|
||||
ctx := NewSimpleFuncStore()
|
||||
// ImportMathFuncs(ctx)
|
||||
// ImportImportFunc(ctx)
|
||||
// ImportOsFuncs(ctx)
|
||||
parser := NewParser(ctx)
|
||||
|
||||
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
|
||||
|
||||
r := strings.NewReader(input.source)
|
||||
scanner := NewScanner(r, DefaultTranslations())
|
||||
|
||||
good := true
|
||||
if expr, gotErr = parser.Parse(scanner); gotErr == nil {
|
||||
gotResult, gotErr = expr.Eval(ctx)
|
||||
}
|
||||
|
||||
if gotResult != input.wantResult {
|
||||
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
||||
good = false
|
||||
}
|
||||
|
||||
if gotErr != input.wantErr {
|
||||
if input.wantErr == nil || gotErr == nil || (gotErr.Error() != input.wantErr.Error()) {
|
||||
t.Errorf("%d: %q -> err = <%v>, want <%v>", i+1, input.source, gotErr, input.wantErr)
|
||||
good = false
|
||||
}
|
||||
}
|
||||
|
||||
if good {
|
||||
succeeded++
|
||||
} else {
|
||||
failed++
|
||||
}
|
||||
}
|
||||
t.Log(fmt.Sprintf("test count: %d, succeeded count: %d, failed count: %d", len(inputs), succeeded, failed))
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
+21
-18
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// helpers.go
|
||||
package expr
|
||||
|
||||
@@ -19,35 +22,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// helpers_test.go
|
||||
package expr
|
||||
|
||||
@@ -24,7 +27,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)},
|
||||
@@ -45,7 +48,6 @@ func TestEvalStringA(t *testing.T) {
|
||||
t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult)
|
||||
t.Errorf("Error: %v", gotErr)
|
||||
}
|
||||
fmt.Println("Hello World!")
|
||||
}
|
||||
|
||||
func TestEvalString(t *testing.T) {
|
||||
@@ -76,5 +78,4 @@ func TestEvalString(t *testing.T) {
|
||||
t.Errorf("Source %q got %v, want %v", source, gotResult, wantResult)
|
||||
t.Errorf("Error: %v", gotErr)
|
||||
}
|
||||
fmt.Println("Hello World!")
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// it-range.go
|
||||
package expr
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// iter-list.go
|
||||
package expr
|
||||
|
||||
import "io"
|
||||
|
||||
type FlatArrayIterator struct {
|
||||
a []any
|
||||
index int
|
||||
}
|
||||
|
||||
func NewFlatArrayIterator(array []any) *FlatArrayIterator {
|
||||
return &FlatArrayIterator{a: array, index: 0}
|
||||
}
|
||||
|
||||
func (it *FlatArrayIterator) Current() (item any, err error) {
|
||||
if it.index >= 0 && it.index < len(it.a) {
|
||||
item = it.a[it.index]
|
||||
} else {
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (it *FlatArrayIterator) Next() (item any, err error) {
|
||||
if item, err = it.Current(); err != io.EOF {
|
||||
it.index++
|
||||
}
|
||||
// if it.index < len(it.a) {
|
||||
// item = it.a[it.index]
|
||||
// it.index++
|
||||
// } else {
|
||||
// err = io.EOF
|
||||
// }
|
||||
return
|
||||
}
|
||||
|
||||
func (it *FlatArrayIterator) Index() int {
|
||||
return it.index - 1
|
||||
}
|
||||
+4
-30
@@ -1,37 +1,11 @@
|
||||
// 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
|
||||
Current() (item any, err error)
|
||||
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
|
||||
}
|
||||
|
||||
+7
-48
@@ -4,22 +4,8 @@
|
||||
// operand-const.go
|
||||
package expr
|
||||
|
||||
// -------- bool const term
|
||||
func newBoolTerm(tk *Token) *term {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
// class: classConst,
|
||||
// kind: kindBool,
|
||||
parent: nil,
|
||||
children: nil,
|
||||
position: posLeaf,
|
||||
priority: priValue,
|
||||
evalFunc: evalConst,
|
||||
}
|
||||
}
|
||||
|
||||
// -------- integer const term
|
||||
func newIntegerTerm(tk *Token) *term {
|
||||
// -------- const term
|
||||
func newConstTerm(tk *Token) *term {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
parent: nil,
|
||||
@@ -30,34 +16,6 @@ func newIntegerTerm(tk *Token) *term {
|
||||
}
|
||||
}
|
||||
|
||||
// -------- float const term
|
||||
func newFloatTerm(tk *Token) *term {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
// class: classConst,
|
||||
// kind: kindFloat,
|
||||
parent: nil,
|
||||
children: nil,
|
||||
position: posLeaf,
|
||||
priority: priValue,
|
||||
evalFunc: evalConst,
|
||||
}
|
||||
}
|
||||
|
||||
// -------- string const term
|
||||
func newStringTerm(tk *Token) *term {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
// class: classConst,
|
||||
// kind: kindString,
|
||||
parent: nil,
|
||||
children: nil,
|
||||
position: posLeaf,
|
||||
priority: priValue,
|
||||
evalFunc: evalConst,
|
||||
}
|
||||
}
|
||||
|
||||
// -------- eval func
|
||||
func evalConst(ctx ExprContext, self *term) (v any, err error) {
|
||||
v = self.tk.Value
|
||||
@@ -66,8 +24,9 @@ func evalConst(ctx ExprContext, self *term) (v any, err error) {
|
||||
|
||||
// init
|
||||
func init() {
|
||||
registerTermConstructor(SymString, newStringTerm)
|
||||
registerTermConstructor(SymInteger, newIntegerTerm)
|
||||
registerTermConstructor(SymFloat, newFloatTerm)
|
||||
registerTermConstructor(SymBool, newBoolTerm)
|
||||
registerTermConstructor(SymString, newConstTerm)
|
||||
registerTermConstructor(SymInteger, newConstTerm)
|
||||
registerTermConstructor(SymFloat, newConstTerm)
|
||||
registerTermConstructor(SymBool, newConstTerm)
|
||||
registerTermConstructor(SymKwNil, newConstTerm)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// operand-dict.go
|
||||
package expr
|
||||
|
||||
// -------- dict term
|
||||
func newDictTerm(args map[any]*term) *term {
|
||||
return &term{
|
||||
tk: *NewValueToken(0, 0, SymDict, "{}", args),
|
||||
parent: nil,
|
||||
children: nil,
|
||||
position: posLeaf,
|
||||
priority: priValue,
|
||||
evalFunc: evalDict,
|
||||
}
|
||||
}
|
||||
|
||||
// -------- dict func
|
||||
func evalDict(ctx ExprContext, self *term) (v any, err error) {
|
||||
dict, _ := self.value().(map[any]*term)
|
||||
items := make(map[any]any, len(dict))
|
||||
for key, tree := range dict {
|
||||
var param any
|
||||
if param, err = tree.compute(ctx); err != nil {
|
||||
break
|
||||
}
|
||||
items[key] = param
|
||||
}
|
||||
if err == nil {
|
||||
v = items
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -27,8 +27,3 @@ func evalExpr(ctx ExprContext, self *term) (v any, err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// init
|
||||
// func init() {
|
||||
// registerTermConstructor(SymExpression, newExprTerm)
|
||||
// }
|
||||
|
||||
+10
-38
@@ -11,9 +11,7 @@ import (
|
||||
// -------- function call term
|
||||
func newFuncCallTerm(tk *Token, args []*term) *term {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
// class: classVar,
|
||||
// kind: kindUnknown,
|
||||
tk: *tk,
|
||||
parent: nil,
|
||||
children: args,
|
||||
position: posLeaf,
|
||||
@@ -24,7 +22,7 @@ func newFuncCallTerm(tk *Token, args []*term) *term {
|
||||
|
||||
// -------- eval func call
|
||||
func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
|
||||
ctx := parentCtx.Clone()
|
||||
ctx := cloneContext(parentCtx)
|
||||
name, _ := self.tk.Value.(string)
|
||||
params := make([]any, len(self.children))
|
||||
for i, tree := range self.children {
|
||||
@@ -36,37 +34,12 @@ func evalFuncCall(parentCtx ExprContext, self *term) (v any, err error) {
|
||||
}
|
||||
if err == nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
exportObjects(parentCtx, ctx)
|
||||
}
|
||||
}
|
||||
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{
|
||||
@@ -89,9 +62,14 @@ 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])
|
||||
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)
|
||||
ctx.setVar(p, nil)
|
||||
}
|
||||
}
|
||||
result, err = functor.expr.eval(ctx, false)
|
||||
@@ -104,12 +82,6 @@ func evalFuncDef(ctx ExprContext, self *term) (v any, err error) {
|
||||
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,
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// operand-iterator.go
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// -------- iterator term
|
||||
|
||||
func newIteratorTerm(tk *Token, dsTerm *term, args []*term) *term {
|
||||
tk.Sym = SymIterator
|
||||
|
||||
children := make([]*term, 0, 1+len(args))
|
||||
children = append(children, dsTerm)
|
||||
children = append(children, args...)
|
||||
return &term{
|
||||
tk: *tk,
|
||||
parent: nil,
|
||||
children: children,
|
||||
position: posLeaf,
|
||||
priority: priValue,
|
||||
evalFunc: evalIterator,
|
||||
}
|
||||
}
|
||||
|
||||
// -------- eval iterator
|
||||
|
||||
func evalTermArray(ctx ExprContext, a []*term) (values []any, err error) {
|
||||
values = make([]any, len(a))
|
||||
for i, t := range a {
|
||||
var value any
|
||||
if value, err = t.compute(ctx); err == nil {
|
||||
values[i] = value
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getDataSourceDict(ctx ExprContext, self *term) (ds map[string]Functor, err error) {
|
||||
var value any
|
||||
if len(self.children) < 1 || self.children[0] == nil {
|
||||
err = self.Errorf("missing the data-source parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if value, err = self.children[0].compute(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if dictAny, ok := value.(map[any]any); ok {
|
||||
ds = make(map[string]Functor)
|
||||
for _, k := range []string{initName, currentName, nextName} {
|
||||
if item, exists := dictAny[k]; exists && item != nil {
|
||||
if functor, ok := item.(*funcDefFunctor); ok {
|
||||
ds[k] = functor
|
||||
}
|
||||
} else if k != initName {
|
||||
err = fmt.Errorf("the data-source must provide a non-nil %q operator", k)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = self.Errorf("the first param (data-source) of an iterator must be a dict, not a %T", value)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func evalIterator(ctx ExprContext, self *term) (v any, err error) {
|
||||
var ds map[string]Functor
|
||||
|
||||
if ds, err = getDataSourceDict(ctx, self); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dc := newDataCursor(ctx)
|
||||
|
||||
if initFunc, exists := ds[initName]; exists && initFunc != nil {
|
||||
var args []any
|
||||
if len(self.children) > 1 {
|
||||
if args, err = evalTermArray(ctx, self.children[1:]); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
args = []any{}
|
||||
}
|
||||
|
||||
initCtx := dc.ctx.Clone()
|
||||
if dc.resource, err = initFunc.Invoke(initCtx, initName, args); err != nil {
|
||||
return
|
||||
}
|
||||
exportObjects(dc.ctx, initCtx)
|
||||
}
|
||||
|
||||
dc.nextFunc, _ = ds[nextName]
|
||||
dc.currentFunc, _ = ds[currentName]
|
||||
v = dc
|
||||
|
||||
return
|
||||
}
|
||||
+37
-4
@@ -5,11 +5,15 @@
|
||||
package expr
|
||||
|
||||
// -------- list term
|
||||
func newListTermA(args ...*term) *term {
|
||||
return newListTerm(args)
|
||||
}
|
||||
|
||||
func newListTerm(args []*term) *term {
|
||||
return &term{
|
||||
tk: *NewToken(0, 0, SymList, "[]"),
|
||||
tk: *NewValueToken(0, 0, SymList, "[]", args),
|
||||
parent: nil,
|
||||
children: args,
|
||||
children: nil,
|
||||
position: posLeaf,
|
||||
priority: priValue,
|
||||
evalFunc: evalList,
|
||||
@@ -18,8 +22,9 @@ func newListTerm(args []*term) *term {
|
||||
|
||||
// -------- list func
|
||||
func evalList(ctx ExprContext, self *term) (v any, err error) {
|
||||
items := make([]any, len(self.children))
|
||||
for i, tree := range self.children {
|
||||
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
|
||||
@@ -31,3 +36,31 @@ func evalList(ctx ExprContext, self *term) (v any, err error) {
|
||||
}
|
||||
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
|
||||
// }
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
// operand-selector-case.go
|
||||
package expr
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// -------- selector case term
|
||||
|
||||
@@ -13,6 +16,18 @@ type selectorCase struct {
|
||||
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{
|
||||
|
||||
+7
-2
@@ -23,8 +23,13 @@ func newVarTerm(tk *Token) *term {
|
||||
// -------- eval func
|
||||
func evalVar(ctx ExprContext, self *term) (v any, err error) {
|
||||
var exists bool
|
||||
if v, exists = ctx.GetVar(self.tk.source); !exists {
|
||||
err = fmt.Errorf("undefined variable %q", self.tk.source)
|
||||
name := self.source()
|
||||
if v, exists = ctx.GetVar(name); !exists {
|
||||
if info, exists := ctx.GetFuncInfo(name); exists {
|
||||
v = info.Functor()
|
||||
} else {
|
||||
err = fmt.Errorf("undefined variable or function %q", name)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
+1
-1
@@ -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.source(), v)
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
+2
-2
@@ -48,7 +48,7 @@ func newAndTerm(tk *Token) (inst *term) {
|
||||
}
|
||||
|
||||
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)
|
||||
} else {
|
||||
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) {
|
||||
if isEnabled(ctx, control_bool_shortcut) {
|
||||
if isEnabled(ctx, ControlBoolShortcut) {
|
||||
v, err = evalOrWithShortcut(ctx, self)
|
||||
} else {
|
||||
v, err = evalOrWithoutShortcut(ctx, self)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
+11
-11
@@ -34,11 +34,11 @@ func evalNullCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
||||
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
|
||||
}
|
||||
// if _, ok := rightValue.(Functor); ok {
|
||||
// err = errCoalesceNoFunc(self.children[1])
|
||||
// } else {
|
||||
v = rightValue
|
||||
// }
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -71,20 +71,20 @@ func evalAssignCoalesce(ctx ExprContext, self *term) (v any, err error) {
|
||||
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])
|
||||
if functor, ok := rightValue.(Functor); ok {
|
||||
ctx.RegisterFunc(leftTerm.source(), functor, 0, -1)
|
||||
} else {
|
||||
v = rightValue
|
||||
ctx.SetVar(leftTerm.source(), 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")
|
||||
}
|
||||
// func errCoalesceNoFunc(t *term) error {
|
||||
// return t.Errorf("the right operand of a coalescing operation cannot be a function definition")
|
||||
// }
|
||||
|
||||
// init
|
||||
func init() {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// operator-context-value.go
|
||||
package expr
|
||||
|
||||
//-------- context term
|
||||
|
||||
func newContextTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
children: make([]*term, 0, 1),
|
||||
position: posPrefix,
|
||||
priority: priPrePost,
|
||||
evalFunc: evalContextValue,
|
||||
}
|
||||
}
|
||||
|
||||
func evalContextValue(ctx ExprContext, self *term) (v any, err error) {
|
||||
var childValue any
|
||||
|
||||
var sourceCtx ExprContext
|
||||
if len(self.children) == 0 {
|
||||
sourceCtx = ctx
|
||||
} else if childValue, err = self.evalPrefix(ctx); err == nil {
|
||||
if dc, ok := childValue.(*dataCursor); ok {
|
||||
sourceCtx = dc.ctx
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
if sourceCtx != nil {
|
||||
keys := sourceCtx.EnumVars(func(name string) bool { return name[0] != '_' })
|
||||
d := make(map[string]any)
|
||||
for _, key := range keys {
|
||||
d[key], _ = sourceCtx.GetVar(key)
|
||||
}
|
||||
v = d
|
||||
} else {
|
||||
err = self.errIncompatibleType(childValue)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// init
|
||||
func init() {
|
||||
registerTermConstructor(SymDoubleDollar, newContextTerm)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// operator-dot.go
|
||||
package expr
|
||||
|
||||
import "fmt"
|
||||
|
||||
// -------- dot term
|
||||
func newDotTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
children: make([]*term, 0, 2),
|
||||
position: posInfix,
|
||||
priority: priDot,
|
||||
evalFunc: evalDot,
|
||||
}
|
||||
}
|
||||
|
||||
func evalDot(ctx ExprContext, self *term) (v any, err error) {
|
||||
var leftValue, rightValue any
|
||||
|
||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
indexTerm := self.children[1]
|
||||
|
||||
if isList(leftValue) {
|
||||
var index int
|
||||
if index, err = indexTerm.toInt(rightValue, "index expression value must be integer"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
list, _ := leftValue.([]any)
|
||||
if index >= 0 && index < len(list) {
|
||||
v = list[index]
|
||||
} else if index >= -len(list) {
|
||||
v = list[len(list)+index]
|
||||
} else {
|
||||
err = indexTerm.Errorf("index %v out of bounds", index)
|
||||
}
|
||||
} else if isString(leftValue) {
|
||||
var index int
|
||||
if index, err = indexTerm.toInt(rightValue, "index expression value must be integer"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s, _ := leftValue.(string)
|
||||
if index >= 0 && index < len(s) {
|
||||
v = string(s[index])
|
||||
} else if index >= -len(s) {
|
||||
v = string(s[len(s)+index])
|
||||
} else {
|
||||
err = indexTerm.Errorf("index %v out of bounds", index)
|
||||
}
|
||||
} else if isDict(leftValue) {
|
||||
var ok bool
|
||||
d, _ := leftValue.(map[any]any)
|
||||
if v, ok = d[rightValue]; !ok {
|
||||
err = fmt.Errorf("key %v does not belong to the dictionary", rightValue)
|
||||
}
|
||||
} else {
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// init
|
||||
func init() {
|
||||
registerTermConstructor(SymDot, newDotTerm)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// operator-iter-value.go
|
||||
package expr
|
||||
|
||||
|
||||
//-------- iter value term
|
||||
|
||||
func newIterValueTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
children: make([]*term, 0, 1),
|
||||
position: posPrefix,
|
||||
priority: priIterValue,
|
||||
evalFunc: evalIterValue,
|
||||
}
|
||||
}
|
||||
|
||||
func evalIterValue(ctx ExprContext, self *term) (v any, err error) {
|
||||
var leftValue any
|
||||
|
||||
if leftValue, err = self.evalPrefix(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if dc, ok := leftValue.(*dataCursor); ok {
|
||||
v, err = dc.Current()
|
||||
} else {
|
||||
err = self.errIncompatibleType(leftValue)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// init
|
||||
func init() {
|
||||
registerTermConstructor(SymOpenClosedRound, newIterValueTerm)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// operator-length.go
|
||||
package expr
|
||||
|
||||
//-------- length term
|
||||
|
||||
func newLengthTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
children: make([]*term, 0, 1),
|
||||
position: posPrefix,
|
||||
priority: priSign,
|
||||
evalFunc: evalLength,
|
||||
}
|
||||
}
|
||||
|
||||
func evalLength(ctx ExprContext, self *term) (v any, err error) {
|
||||
var rightValue any
|
||||
|
||||
if rightValue, err = self.evalPrefix(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if isList(rightValue) {
|
||||
list, _ := rightValue.([]any)
|
||||
v = len(list)
|
||||
} else if isString(rightValue) {
|
||||
s, _ := rightValue.(string)
|
||||
v = len(s)
|
||||
} else if it, ok := rightValue.(Iterator); ok {
|
||||
v = it.Index()
|
||||
} else {
|
||||
err = self.errIncompatibleType(rightValue)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// init
|
||||
func init() {
|
||||
registerTermConstructor(SymHash, newLengthTerm)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// operator-post-inc.go
|
||||
package expr
|
||||
|
||||
// -------- post increment term
|
||||
|
||||
func newPostIncTerm(tk *Token) *term {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
parent: nil,
|
||||
children: make([]*term, 0, 1),
|
||||
position: posPostfix,
|
||||
priority: priPrePost,
|
||||
evalFunc: evalPostInc,
|
||||
}
|
||||
}
|
||||
|
||||
func evalPostInc(ctx ExprContext, self *term) (v any, err error) {
|
||||
var leftValue any
|
||||
if leftValue, err = self.evalPrefix(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if dc, ok := leftValue.(*dataCursor); ok {
|
||||
v, err = dc.Next()
|
||||
} else if isInteger(leftValue) && self.children[0].symbol() == SymIdentifier {
|
||||
v = leftValue
|
||||
i, _ := leftValue.(int64)
|
||||
ctx.SetVar(self.children[0].source(), i+1)
|
||||
} else {
|
||||
self.errIncompatibleType(leftValue)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// init
|
||||
func init() {
|
||||
registerTermConstructor(SymDoublePlus, newPostIncTerm)
|
||||
}
|
||||
+12
-9
@@ -4,31 +4,33 @@
|
||||
// operator-selector.go
|
||||
package expr
|
||||
|
||||
//-------- export all term
|
||||
//-------- selector term
|
||||
|
||||
func newSelectorTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
children: make([]*term, 0, 3),
|
||||
position: posMultifix,
|
||||
position: posInfix,
|
||||
priority: priSelector,
|
||||
evalFunc: evalSelector,
|
||||
}
|
||||
}
|
||||
|
||||
func isSelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (selectedValue any, err error) {
|
||||
func trySelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (match bool, selectedValue any, err error) {
|
||||
caseData, _ := caseSel.(*selectorCase)
|
||||
if caseData.filterList == nil {
|
||||
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
||||
} else {
|
||||
filterList := caseData.filterList.children
|
||||
match = true
|
||||
} else if filterList, ok := caseData.filterList.value().([]*term); ok {
|
||||
if len(filterList) == 0 && exprValue == int64(caseIndex) {
|
||||
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
||||
match = true
|
||||
} else {
|
||||
var caseValue any
|
||||
for _, caseTerm := range filterList {
|
||||
if caseValue, err = caseTerm.compute(ctx); err != nil || caseValue == exprValue {
|
||||
selectedValue, err = caseData.caseExpr.eval(ctx, false)
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -39,7 +41,7 @@ func isSelectorCase(ctx ExprContext, exprValue, caseSel any, caseIndex int) (sel
|
||||
|
||||
func evalSelector(ctx ExprContext, self *term) (v any, err error) {
|
||||
var exprValue any
|
||||
// var caseList []*term
|
||||
var match bool
|
||||
|
||||
if err = self.checkOperands(); err != nil {
|
||||
return
|
||||
@@ -49,14 +51,15 @@ func evalSelector(ctx ExprContext, self *term) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
caseList := self.children[1:]
|
||||
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 {
|
||||
if match, v, err = trySelectorCase(ctx, exprValue, caseSel, i); err != nil || match {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err == nil && v == nil {
|
||||
if err == nil && !match {
|
||||
err = exprTerm.tk.Errorf("no case catches the value (%v) of the selection expression", exprValue)
|
||||
}
|
||||
return
|
||||
|
||||
+1
-3
@@ -65,9 +65,7 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
|
||||
|
||||
func newMinusTerm(tk *Token) (inst *term) {
|
||||
return &term{
|
||||
tk: *tk,
|
||||
// class: classOperator,
|
||||
// kind: kindUnknown,
|
||||
tk: *tk,
|
||||
children: make([]*term, 0, 2),
|
||||
position: posInfix,
|
||||
priority: priSum,
|
||||
|
||||
@@ -58,23 +58,72 @@ func (self *parser) parseFuncCall(scanner *scanner, allowVarRef bool, tk *Token)
|
||||
return
|
||||
}
|
||||
|
||||
// func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
||||
// // Example: "add = func(x,y) {x+y}
|
||||
// var body *ast
|
||||
// args := make([]*term, 0)
|
||||
// tk := scanner.Next()
|
||||
// for tk.Sym != SymClosedRound && tk.Sym != SymEos {
|
||||
// if tk.Sym == SymIdentifier {
|
||||
// t := newTerm(tk, nil)
|
||||
// args = append(args, t)
|
||||
// } else {
|
||||
// err = tk.Errorf("invalid param %q, variable identifier expected", tk.source)
|
||||
// break
|
||||
// }
|
||||
// tk = scanner.Next()
|
||||
// }
|
||||
// if err == nil && tk.Sym != SymClosedRound {
|
||||
// err = tk.Errorf("unterminate function params list")
|
||||
// }
|
||||
// if err == nil {
|
||||
// tk = scanner.Next()
|
||||
// if tk.Sym == SymOpenBrace {
|
||||
// body, err = self.parseGeneral(scanner, true, true, SymClosedBrace)
|
||||
// }
|
||||
// }
|
||||
// if err == nil {
|
||||
// // TODO Check arguments
|
||||
// if scanner.Previous().Sym != SymClosedBrace {
|
||||
// err = scanner.Previous().Errorf("not properly terminated function body")
|
||||
// } else {
|
||||
// tk = scanner.makeValueToken(SymExpression, "", body)
|
||||
// tree = newFuncDefTerm(tk, args)
|
||||
// }
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
||||
// Example: "add = func(x,y) {x+y}
|
||||
var body *ast
|
||||
args := make([]*term, 0)
|
||||
tk := scanner.Next()
|
||||
for tk.Sym != SymClosedRound && tk.Sym != SymEos {
|
||||
if tk.Sym == SymIdentifier {
|
||||
t := newTerm(tk, nil)
|
||||
args = append(args, t)
|
||||
lastSym := SymUnknown
|
||||
itemExpected := false
|
||||
tk := scanner.Previous()
|
||||
for lastSym != SymClosedRound && lastSym != SymEos {
|
||||
var subTree *ast
|
||||
if subTree, err = self.parseItem(scanner, true, SymComma, SymClosedRound); err == nil {
|
||||
if subTree.root != nil {
|
||||
if subTree.root.symbol() == SymIdentifier {
|
||||
args = append(args, subTree.root)
|
||||
} else {
|
||||
err = tk.Errorf("exptected identifier, got %q", subTree.root)
|
||||
}
|
||||
} else if itemExpected {
|
||||
prev := scanner.Previous()
|
||||
err = prev.Errorf("expected function parameter, got %q", prev)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
err = tk.Errorf("invalid param %q, variable identifier expected", tk.source)
|
||||
break
|
||||
}
|
||||
tk = scanner.Next()
|
||||
lastSym = scanner.Previous().Sym
|
||||
itemExpected = lastSym == SymComma
|
||||
}
|
||||
if err == nil && tk.Sym != SymClosedRound {
|
||||
err = tk.Errorf("unterminate function params list")
|
||||
|
||||
if err == nil && lastSym != SymClosedRound {
|
||||
err = tk.Errorf("unterminated function parameters list")
|
||||
}
|
||||
if err == nil {
|
||||
tk = scanner.Next()
|
||||
@@ -97,16 +146,22 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
||||
func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
||||
args := make([]*term, 0)
|
||||
lastSym := SymUnknown
|
||||
itemExpected := false
|
||||
for lastSym != SymClosedSquare && lastSym != SymEos {
|
||||
var subTree *ast
|
||||
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
|
||||
if subTree.root != nil {
|
||||
args = append(args, subTree.root)
|
||||
} else if itemExpected {
|
||||
prev := scanner.Previous()
|
||||
err = prev.Errorf("expected list item, got %q", prev)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
lastSym = scanner.Previous().Sym
|
||||
itemExpected = lastSym == SymComma
|
||||
}
|
||||
if err == nil {
|
||||
// TODO Check arguments
|
||||
@@ -119,6 +174,115 @@ func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term
|
||||
return
|
||||
}
|
||||
|
||||
func (self *parser) parseIterDef(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
||||
var ds *term
|
||||
tk := scanner.Previous()
|
||||
args := make([]*term, 0)
|
||||
lastSym := SymUnknown
|
||||
dsExpected := true
|
||||
itemExpected := false
|
||||
for lastSym != SymClosedRound && lastSym != SymEos {
|
||||
var subTree *ast
|
||||
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedRound); err == nil {
|
||||
if subTree.root != nil {
|
||||
if dsExpected {
|
||||
if sym := subTree.root.symbol(); sym == SymDict || sym == SymIdentifier {
|
||||
ds = subTree.root
|
||||
} else {
|
||||
err = subTree.root.Errorf("data-source dictionary expected, got %q", subTree.root.source())
|
||||
}
|
||||
dsExpected = false
|
||||
} else {
|
||||
args = append(args, subTree.root)
|
||||
}
|
||||
} else if itemExpected {
|
||||
prev := scanner.Previous()
|
||||
err = prev.Errorf("expected iterator argument, got %q", prev)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
lastSym = scanner.Previous().Sym
|
||||
itemExpected = lastSym == SymComma
|
||||
}
|
||||
if err == nil {
|
||||
// TODO Check arguments
|
||||
if lastSym != SymClosedRound {
|
||||
err = scanner.Previous().Errorf("unterminate iterator param list")
|
||||
} else if ds != nil {
|
||||
subtree = newIteratorTerm(tk, ds, args)
|
||||
} else {
|
||||
tk.Errorf("missing data-source param")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *parser) parseDictKey(scanner *scanner, allowVarRef bool) (key any, err error) {
|
||||
tk := scanner.Next()
|
||||
if tk.Sym == SymError {
|
||||
err = tk.Error()
|
||||
return
|
||||
}
|
||||
if tk.Sym == SymClosedBrace || tk.Sym == SymEos {
|
||||
return
|
||||
}
|
||||
if tk.Sym == SymInteger || tk.Sym == SymString {
|
||||
tkSep := scanner.Next()
|
||||
if tkSep.Sym != SymColon {
|
||||
err = tkSep.Errorf("expected \":\", got %q", tkSep)
|
||||
} else {
|
||||
key = tk.Value
|
||||
}
|
||||
} else {
|
||||
err = tk.Errorf("expected dictionary key or closed brace, got %q", tk)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *parser) parseDictionary(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
||||
args := make(map[any]*term, 0)
|
||||
lastSym := SymUnknown
|
||||
itemExpected := false
|
||||
for lastSym != SymClosedBrace && lastSym != SymEos {
|
||||
var subTree *ast
|
||||
var key any
|
||||
if key, err = self.parseDictKey(scanner, allowVarRef); err != nil {
|
||||
break
|
||||
} else if key == nil {
|
||||
tk := scanner.Previous()
|
||||
lastSym = tk.Sym
|
||||
if itemExpected {
|
||||
err = tk.Errorf("expected dictionary key, got %q", tk)
|
||||
}
|
||||
break
|
||||
}
|
||||
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedBrace); err == nil {
|
||||
if subTree.root != nil {
|
||||
args[key] = subTree.root
|
||||
} else if key != nil {
|
||||
prev := scanner.Previous()
|
||||
err = prev.Errorf("expected dictionary value, got %q", prev)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
lastSym = scanner.Previous().Sym
|
||||
itemExpected = lastSym == SymComma
|
||||
}
|
||||
if err == nil {
|
||||
// TODO Check arguments
|
||||
if lastSym != SymClosedBrace {
|
||||
err = scanner.Previous().Errorf("unterminated dictionary")
|
||||
} else {
|
||||
subtree = newDictTerm(args)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaultCase bool) (caseTerm *term, err error) {
|
||||
var filterList *term
|
||||
var caseExpr *ast
|
||||
@@ -154,6 +318,19 @@ func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaul
|
||||
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, '?')
|
||||
@@ -162,8 +339,7 @@ func (self *parser) parseSelector(scanner *scanner, tree *ast, allowVarRef bool)
|
||||
}
|
||||
|
||||
if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, false); err == nil {
|
||||
selectorTerm.children = append(selectorTerm.children, caseTerm)
|
||||
caseTerm.parent = selectorTerm
|
||||
addSelectorCase(selectorTerm, caseTerm)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -179,14 +355,14 @@ func (self *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, e
|
||||
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() {
|
||||
for tk = scanner.Next(); err == nil && tk != nil && !tk.IsTerm(termSymbols); tk = scanner.Next() {
|
||||
if tk.Sym == SymComment {
|
||||
continue
|
||||
}
|
||||
//resetSelector := true
|
||||
|
||||
if tk.Sym == SymSemiColon {
|
||||
if allowForest {
|
||||
@@ -229,6 +405,12 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
||||
err = tree.addTerm(listTerm)
|
||||
currentTerm = listTerm
|
||||
}
|
||||
case SymOpenBrace:
|
||||
var mapTerm *term
|
||||
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
|
||||
err = tree.addTerm(mapTerm)
|
||||
currentTerm = mapTerm
|
||||
}
|
||||
case SymEqual:
|
||||
if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
|
||||
currentTerm, err = tree.addToken2(tk)
|
||||
@@ -239,6 +421,12 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
||||
err = tree.addTerm(funcDefTerm)
|
||||
currentTerm = funcDefTerm
|
||||
}
|
||||
case SymDollarRound:
|
||||
var iterDefTerm *term
|
||||
if iterDefTerm, err = self.parseIterDef(scanner, allowVarRef); err == nil {
|
||||
err = tree.addTerm(iterDefTerm)
|
||||
currentTerm = iterDefTerm
|
||||
}
|
||||
case SymIdentifier:
|
||||
if tk.source[0] == '@' && !allowVarRef {
|
||||
err = tk.Errorf("variable references are not allowed in top level expressions: %q", tk.source)
|
||||
@@ -246,9 +434,7 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
||||
currentTerm, err = tree.addToken2(tk)
|
||||
}
|
||||
case SymQuestion:
|
||||
/*if selectorTerm != nil {
|
||||
err = tk.Errorf("nested selectors must be enclosed in parentheses")
|
||||
} else*/if selectorTerm, err = self.parseSelector(scanner, tree, allowVarRef); err == nil {
|
||||
if selectorTerm, err = self.parseSelector(scanner, tree, allowVarRef); err == nil {
|
||||
currentTerm = selectorTerm
|
||||
}
|
||||
case SymColon, SymDoubleColon:
|
||||
@@ -256,27 +442,25 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
||||
if selectorTerm == nil {
|
||||
err = tk.Errorf("selector-case outside of a selector context")
|
||||
} else if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
|
||||
selectorTerm.children = append(selectorTerm.children, caseTerm)
|
||||
caseTerm.parent = selectorTerm
|
||||
addSelectorCase(selectorTerm, caseTerm)
|
||||
currentTerm = caseTerm
|
||||
if tk.Sym == SymDoubleColon {
|
||||
selectorTerm = nil
|
||||
}
|
||||
}
|
||||
//resetSelector = tk.Sym == SymDoubleColon
|
||||
default:
|
||||
currentTerm, err = tree.addToken2(tk)
|
||||
}
|
||||
|
||||
if currentTerm != nil && currentTerm.parent != nil && currentTerm.parent.tk.Sym != SymSelector {
|
||||
if currentTerm != nil && currentTerm.tk.Sym != SymSelector && currentTerm.parent != nil && currentTerm.parent.tk.Sym != SymSelector {
|
||||
selectorTerm = nil
|
||||
|
||||
}
|
||||
// if resetSelector {
|
||||
// selectorTree = nil
|
||||
// }
|
||||
lastSym = tk.Sym
|
||||
}
|
||||
if err == nil {
|
||||
err = tk.Error()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+36
-26
@@ -7,6 +7,7 @@ package expr
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -63,8 +64,8 @@ func TestParser(t *testing.T) {
|
||||
/* 42 */ {`"s" + true`, nil, errors.New(`[1:6] left operand 's' [string] and right operand 'true' [bool] are not compatible with operator "+"`)},
|
||||
/* 43 */ {`+false`, nil, errors.New(`[1:2] prefix/postfix operator "+" do not support operand 'false' [bool]`)},
|
||||
/* 44 */ {`false // very simple expression`, false, nil},
|
||||
/* 45 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two not nil operands, got 1`)},
|
||||
/* 46 */ {"", nil, errors.New(`empty expression`)},
|
||||
/* 45 */ {`1 + // Missing right operator`, nil, errors.New(`[1:4] infix operator "+" requires two non-nil operands, got 1`)},
|
||||
/* 46 */ {"", nil, nil},
|
||||
/* 47 */ {"4!", int64(24), nil},
|
||||
/* 48 */ {"(-4)!", nil, errors.New(`factorial of a negative integer (-4) is not allowed`)},
|
||||
/* 49 */ {"-4!", int64(-24), nil},
|
||||
@@ -78,16 +79,16 @@ func TestParser(t *testing.T) {
|
||||
/* 57 */ {`"1.5" > "7"`, false, nil},
|
||||
/* 58 */ {`"1.5" == "7"`, false, nil},
|
||||
/* 59 */ {`"1.5" != "7"`, true, nil},
|
||||
/* 60 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two not nil operands, got 1`)},
|
||||
/* 61 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two not nil operands, got 1`)},
|
||||
/* 62 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two not nil operands, got 1`)},
|
||||
/* 63 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two not nil operands, got 1`)},
|
||||
/* 64 */ {"1.5 != ", nil, errors.New(`[1:6] infix operator "!=" requires two not nil operands, got 1`)},
|
||||
/* 65 */ {"1.5 == ", nil, errors.New(`[1:6] infix operator "==" requires two not nil operands, got 1`)},
|
||||
/* 66 */ {`"1.5" < `, nil, errors.New(`[1:8] infix operator "<" requires two not nil operands, got 1`)},
|
||||
/* 67 */ {`"1.5" > `, nil, errors.New(`[1:8] infix operator ">" requires two not nil operands, got 1`)},
|
||||
/* 68 */ {`"1.5" == `, nil, errors.New(`[1:8] infix operator "==" requires two not nil operands, got 1`)},
|
||||
/* 69 */ {`"1.5" != `, nil, errors.New(`[1:8] infix operator "!=" requires two not nil operands, got 1`)},
|
||||
/* 60 */ {"1.5 < ", nil, errors.New(`[1:6] infix operator "<" requires two non-nil operands, got 1`)},
|
||||
/* 61 */ {"1.5 > ", nil, errors.New(`[1:6] infix operator ">" requires two non-nil operands, got 1`)},
|
||||
/* 62 */ {"1.5 <= ", nil, errors.New(`[1:6] infix operator "<=" requires two non-nil operands, got 1`)},
|
||||
/* 63 */ {"1.5 >= ", nil, errors.New(`[1:6] infix operator ">=" requires two non-nil operands, got 1`)},
|
||||
/* 64 */ {"1.5 != ", nil, errors.New(`[1:6] infix operator "!=" requires two non-nil operands, got 1`)},
|
||||
/* 65 */ {"1.5 == ", nil, errors.New(`[1:6] infix operator "==" requires two non-nil operands, got 1`)},
|
||||
/* 66 */ {`"1.5" < `, nil, errors.New(`[1:8] infix operator "<" requires two non-nil operands, got 1`)},
|
||||
/* 67 */ {`"1.5" > `, nil, errors.New(`[1:8] infix operator ">" requires two non-nil operands, got 1`)},
|
||||
/* 68 */ {`"1.5" == `, nil, errors.New(`[1:8] infix operator "==" requires two non-nil operands, got 1`)},
|
||||
/* 69 */ {`"1.5" != `, nil, errors.New(`[1:8] infix operator "!=" requires two non-nil operands, got 1`)},
|
||||
/* 70 */ {"+1.5", float64(1.5), nil},
|
||||
/* 71 */ {"+", nil, errors.New(`[1:2] prefix operator "+" requires one not nil operand`)},
|
||||
/* 72 */ {"4 / 0", nil, errors.New(`division by zero`)},
|
||||
@@ -117,9 +118,9 @@ func TestParser(t *testing.T) {
|
||||
/* 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"`)},
|
||||
/* 99 */ {`false and (x=2 but x==2) or x==2`, nil, errors.New(`undefined variable or function "x"`)},
|
||||
/* 100 */ {`false or true`, true, nil},
|
||||
/* 101 */ {`false or (x==2)`, nil, errors.New(`undefined variable "x"`)},
|
||||
/* 101 */ {`false or (x==2)`, nil, errors.New(`undefined variable or function "x"`)},
|
||||
/* 102 */ {`a=5; a`, int64(5), nil},
|
||||
/* 103 */ {`a=5; b=2; add(a, b*3)`, int64(11), nil},
|
||||
/* 104 */ {`2=5`, nil, errors.New(`assign operator ("=") must be preceded by a variable`)},
|
||||
@@ -134,13 +135,13 @@ func TestParser(t *testing.T) {
|
||||
/* 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`)},
|
||||
/* 116 */ {`y=x ?? func(){4}; y()`, int64(4), nil},
|
||||
/* 117 */ {`x ?= "default"; x`, "default", nil},
|
||||
/* 118 */ {`x="hello"; x ?= "default"; x`, "hello", nil},
|
||||
/* 119 */ {`@x="hello"; @x`, nil, errors.New(`[1:3] variable references are not allowed in top level expressions: "@x"`)},
|
||||
/* 120 */ {`f=func(){@x="hello"}; f(); x`, "hello", nil},
|
||||
/* 121 */ {`f=func(@y){@y=@y+1}; f(2); y`, int64(3), nil},
|
||||
/* 122 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable "x"`)},
|
||||
/* 122 */ {`f=func(@y){g=func(){@x=5}; @y=@y+g()}; f(2); y+x`, nil, errors.New(`undefined variable or function "x"`)},
|
||||
/* 123 */ {`f=func(@y){g=func(){@x=5}; @z=g(); @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
||||
/* 124 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @y=@y+@z}; f(2); y+z`, int64(12), nil},
|
||||
/* 125 */ {`f=func(@y){g=func(){@x=5}; g(); @z=x; @x=@y+@z}; f(2); y+x`, int64(9), nil},
|
||||
@@ -155,6 +156,13 @@ func TestParser(t *testing.T) {
|
||||
/* 134 */ {`10 ? {"a"} : {"b"}`, nil, errors.New(`[1:3] no case catches the value (10) of the selection expression`)},
|
||||
/* 135 */ {`10 ? {"a"} :: {"b"} : {"c"}`, nil, errors.New(`[1:22] selector-case outside of a selector context`)},
|
||||
/* 136 */ {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
|
||||
/* 137 */ {`2 + 1 ? {"a"} : {"b"} * 3`, "2bbb", nil},
|
||||
/* 138 */ {`nil`, nil, nil},
|
||||
/* 139 */ {`null`, nil, errors.New(`undefined variable or function "null"`)},
|
||||
/* 140 */ {`{"key"}`, nil, errors.New(`[1:8] expected ":", got "}"`)},
|
||||
/* 141 */ {`{"key":}`, nil, errors.New(`[1:9] expected dictionary value, got "}"`)},
|
||||
/* 142 */ {`{}`, map[any]any{}, nil},
|
||||
/* 144 */ //{`3^2`, int64(9), nil},
|
||||
}
|
||||
check_env_expr_path := 113
|
||||
|
||||
@@ -162,7 +170,7 @@ func TestParser(t *testing.T) {
|
||||
failed := 0
|
||||
|
||||
// inputs1 := []inputType{
|
||||
// {`1 ? {"a"} : {"b"} ? ["a"] {"A"} :["b"] {"B"}`, "B", nil},
|
||||
// /* 140 */ {`ds={}; $(ds)`, nil, nil},
|
||||
// }
|
||||
|
||||
for i, input := range inputs {
|
||||
@@ -173,8 +181,8 @@ func TestParser(t *testing.T) {
|
||||
ctx := NewSimpleFuncStore()
|
||||
ctx.SetVar("var1", int64(123))
|
||||
ctx.SetVar("var2", "abc")
|
||||
importMathFuncs(ctx)
|
||||
importImportFunc(ctx)
|
||||
ImportMathFuncs(ctx)
|
||||
ImportImportFuncs(ctx)
|
||||
parser := NewParser(ctx)
|
||||
|
||||
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
|
||||
@@ -187,7 +195,9 @@ func TestParser(t *testing.T) {
|
||||
gotResult, gotErr = expr.Eval(ctx)
|
||||
}
|
||||
|
||||
if gotResult != input.wantResult {
|
||||
eq := reflect.DeepEqual(gotResult, input.wantResult)
|
||||
|
||||
if !eq /*gotResult != input.wantResult*/ {
|
||||
t.Errorf("%d: %q -> result = %v [%T], want %v [%T]", i+1, input.source, gotResult, gotResult, input.wantResult, input.wantResult)
|
||||
good = false
|
||||
}
|
||||
@@ -204,7 +214,7 @@ func TestParser(t *testing.T) {
|
||||
} else {
|
||||
failed++
|
||||
if i+1 == check_env_expr_path {
|
||||
t.Logf(`NOTICE: Test nr %d requires EXPR_PATH environment variable with value "."`, check_env_expr_path)
|
||||
t.Logf(`NOTICE: Test nr %d requires EXPR_PATH="."`, check_env_expr_path)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,10 +228,6 @@ func TestListParser(t *testing.T) {
|
||||
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},
|
||||
@@ -241,6 +247,10 @@ func TestListParser(t *testing.T) {
|
||||
succeeded := 0
|
||||
failed := 0
|
||||
|
||||
// inputs1 := []inputType{
|
||||
// /* 7 */ {`add([1,4,3,2])`, int64(10), nil},
|
||||
// }
|
||||
|
||||
for i, input := range inputs {
|
||||
var expr *ast
|
||||
var gotResult any
|
||||
@@ -249,7 +259,7 @@ func TestListParser(t *testing.T) {
|
||||
ctx := NewSimpleFuncStore()
|
||||
ctx.SetVar("var1", int64(123))
|
||||
ctx.SetVar("var2", "abc")
|
||||
importMathFuncs(ctx)
|
||||
ImportMathFuncs(ctx)
|
||||
parser := NewParser(ctx)
|
||||
|
||||
logTest(t, i+1, input.source, input.wantResult, input.wantErr)
|
||||
|
||||
+114
-38
@@ -86,13 +86,14 @@ func (self *scanner) Next() (tk *Token) {
|
||||
}
|
||||
|
||||
func (self *scanner) fetchNextToken() (tk *Token) {
|
||||
var ch byte
|
||||
if err := self.skipBlanks(); err != nil {
|
||||
return self.makeErrorToken(err)
|
||||
}
|
||||
|
||||
escape := false
|
||||
for {
|
||||
ch, _ := self.readChar()
|
||||
ch, _ = self.readChar()
|
||||
switch ch {
|
||||
case '+':
|
||||
if next, _ := self.peek(); next == '+' {
|
||||
@@ -143,6 +144,8 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
||||
}
|
||||
case ',':
|
||||
tk = self.makeToken(SymComma, ch)
|
||||
case '^':
|
||||
tk = self.makeToken(SymCaret, ch)
|
||||
case ':':
|
||||
if next, _ := self.peek(); next == ':' {
|
||||
tk = self.moveOn(SymDoubleColon, ch, next)
|
||||
@@ -152,9 +155,10 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
||||
case ';':
|
||||
tk = self.makeToken(SymSemiColon, ch)
|
||||
case '.':
|
||||
if next, _ := self.peek(); next >= '0' && next <= '9' {
|
||||
tk = self.parseNumber(ch)
|
||||
} else if next == '/' {
|
||||
//if next, _ := self.peek(); next >= '0' && next <= '9' {
|
||||
// tk = self.parseNumber(ch)
|
||||
//} else if next == '/' {
|
||||
if next, _ := self.peek(); next == '/' {
|
||||
tk = self.moveOn(SymDotSlash, ch, next)
|
||||
} else {
|
||||
tk = self.makeToken(SymDot, ch)
|
||||
@@ -219,6 +223,8 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
||||
case '<':
|
||||
if next, _ := self.peek(); next == '=' {
|
||||
tk = self.moveOn(SymLessOrEqual, ch, next)
|
||||
} else if next == '<' {
|
||||
tk = self.moveOn(SymAppend, ch, next)
|
||||
} else if next == '>' {
|
||||
tk = self.moveOn(SymLessGreater, ch, next)
|
||||
} else {
|
||||
@@ -227,13 +233,26 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
||||
case '>':
|
||||
if next, _ := self.peek(); next == '=' {
|
||||
tk = self.moveOn(SymGreaterOrEqual, ch, next)
|
||||
} else if next == '>' {
|
||||
tk = self.moveOn(SymInsert, ch, next)
|
||||
} else {
|
||||
tk = self.makeToken(SymGreater, ch)
|
||||
}
|
||||
case '$':
|
||||
tk = self.makeToken(SymDollar, ch)
|
||||
if next, _ := self.peek(); next == '(' {
|
||||
tk = self.moveOn(SymDollarRound, ch, next)
|
||||
tk.source += ")"
|
||||
} else if next == '$' {
|
||||
tk = self.moveOn(SymDoubleDollar, ch, next)
|
||||
} else {
|
||||
tk = self.makeToken(SymDollar, ch)
|
||||
}
|
||||
case '(':
|
||||
tk = self.makeToken(SymOpenRound, ch)
|
||||
if next, _ := self.peek(); next == ')' {
|
||||
tk = self.moveOn(SymOpenClosedRound, ch, next)
|
||||
} else {
|
||||
tk = self.makeToken(SymOpenRound, ch)
|
||||
}
|
||||
case ')':
|
||||
tk = self.makeToken(SymClosedRound, ch)
|
||||
case '[':
|
||||
@@ -266,6 +285,9 @@ func (self *scanner) fetchNextToken() (tk *Token) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if tk == nil {
|
||||
tk = NewErrorToken(self.row, self.column, fmt.Errorf("unknown symbol '%c'", ch))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -276,62 +298,116 @@ func (self *scanner) sync(err error) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func isBinaryDigit(ch byte) bool {
|
||||
return ch == '0' || ch == '1'
|
||||
}
|
||||
|
||||
func isOctalDigit(ch byte) bool {
|
||||
return ch >= '0' && ch <= '7'
|
||||
}
|
||||
|
||||
func isDecimalDigit(ch byte) bool {
|
||||
return ch >= '0' && ch <= '9'
|
||||
}
|
||||
|
||||
func isHexDigit(ch byte) bool {
|
||||
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
|
||||
}
|
||||
|
||||
func (self *scanner) initBase(sb *strings.Builder, currentFirstCh byte) (firstCh byte, numBase int, digitFunc func(byte) bool, err error) {
|
||||
var ch byte
|
||||
var digitType string
|
||||
firstCh = currentFirstCh
|
||||
digitFunc = isDecimalDigit
|
||||
numBase = 10
|
||||
|
||||
if ch, err = self.peek(); err == nil {
|
||||
if ch == 'b' || ch == 'B' {
|
||||
numBase = 2
|
||||
digitType = "binary"
|
||||
self.readChar()
|
||||
digitFunc = isBinaryDigit
|
||||
firstCh, err = self.readChar()
|
||||
} else if ch == 'o' || ch == 'O' {
|
||||
numBase = 8
|
||||
digitType = "octal"
|
||||
self.readChar()
|
||||
digitFunc = isOctalDigit
|
||||
firstCh, err = self.readChar()
|
||||
} else if ch == 'x' || ch == 'X' {
|
||||
numBase = 16
|
||||
digitType = "hex"
|
||||
self.readChar()
|
||||
digitFunc = isHexDigit
|
||||
firstCh, err = self.readChar()
|
||||
}
|
||||
if err == nil && !digitFunc(firstCh) {
|
||||
if len(digitType) == 0 {
|
||||
digitType = "decimal"
|
||||
}
|
||||
err = fmt.Errorf("expected %s digit, got '%c'", digitType, firstCh)
|
||||
}
|
||||
} else if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *scanner) parseNumber(firstCh byte) (tk *Token) {
|
||||
var err error
|
||||
var ch byte
|
||||
var sym Symbol = SymInteger
|
||||
var value any
|
||||
var sb strings.Builder
|
||||
var isDigit func(byte) bool = isDecimalDigit
|
||||
var numBase = 10
|
||||
|
||||
for ch = firstCh; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
|
||||
if firstCh == '0' {
|
||||
firstCh, numBase, isDigit, err = self.initBase(&sb, firstCh)
|
||||
}
|
||||
for ch = firstCh; err == nil && isDigit(ch); ch, err = self.readChar() {
|
||||
sb.WriteByte(ch)
|
||||
}
|
||||
if ch == '.' {
|
||||
sym = SymFloat
|
||||
sb.WriteByte(ch)
|
||||
ch, err = self.readChar()
|
||||
if ch >= '0' && ch <= '9' {
|
||||
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
|
||||
sb.WriteByte(ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
if ch == 'e' || ch == 'E' {
|
||||
sym = SymFloat
|
||||
sb.WriteByte(ch)
|
||||
if ch, err = self.readChar(); err == nil {
|
||||
if ch == '+' || ch == '-' {
|
||||
sb.WriteByte(ch)
|
||||
ch, err = self.readChar()
|
||||
}
|
||||
|
||||
if numBase == 10 {
|
||||
if err == nil && ch == '.' {
|
||||
sym = SymFloat
|
||||
sb.WriteByte(ch)
|
||||
ch, err = self.readChar()
|
||||
if ch >= '0' && ch <= '9' {
|
||||
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
|
||||
sb.WriteByte(ch)
|
||||
}
|
||||
//err = self.sync(err)
|
||||
} else {
|
||||
err = errors.New("expected integer exponent")
|
||||
}
|
||||
}
|
||||
// } else {
|
||||
// err = self.sync(err)
|
||||
if err == nil && (ch == 'e' || ch == 'E') {
|
||||
sym = SymFloat
|
||||
sb.WriteByte(ch)
|
||||
if ch, err = self.readChar(); err == nil {
|
||||
if ch == '+' || ch == '-' {
|
||||
sb.WriteByte(ch)
|
||||
ch, err = self.readChar()
|
||||
}
|
||||
if ch >= '0' && ch <= '9' {
|
||||
for ; err == nil && (ch >= '0' && ch <= '9'); ch, err = self.readChar() {
|
||||
sb.WriteByte(ch)
|
||||
}
|
||||
} else {
|
||||
err = errors.New("expected integer exponent")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil && err != io.EOF {
|
||||
tk = self.makeErrorToken(err)
|
||||
} else {
|
||||
var value any
|
||||
err = self.sync(err)
|
||||
txt := sb.String()
|
||||
if sym == SymFloat {
|
||||
value, err = strconv.ParseFloat(txt, 64)
|
||||
} else if strings.HasPrefix(txt, "0x") {
|
||||
value, err = strconv.ParseInt(txt, 16, 64)
|
||||
} else if strings.HasPrefix(txt, "0o") {
|
||||
value, err = strconv.ParseInt(txt, 8, 64)
|
||||
} else if strings.HasPrefix(txt, "0b") {
|
||||
value, err = strconv.ParseInt(txt, 2, 64)
|
||||
} else {
|
||||
value, err = strconv.ParseInt(txt, 10, 64)
|
||||
value, err = strconv.ParseInt(txt, numBase, 64)
|
||||
}
|
||||
if err == nil {
|
||||
tk = self.makeValueToken(sym, txt, value)
|
||||
|
||||
+2
-2
@@ -36,7 +36,7 @@ func TestScanner(t *testing.T) {
|
||||
/* 14 */ {`:`, SymColon, nil, nil},
|
||||
/* 15 */ {`;`, SymSemiColon, nil, nil},
|
||||
/* 16 */ {`.`, SymDot, nil, nil},
|
||||
/* 17 */ {`.5`, SymFloat, float64(0.5), nil},
|
||||
/* 17 */ {`0.5`, SymFloat, float64(0.5), nil},
|
||||
/* 18 */ {`\\`, SymBackSlash, nil, nil},
|
||||
/* 19 */ {"`", SymBackTick, nil, nil},
|
||||
/* 20 */ {"?", SymQuestion, nil, nil},
|
||||
@@ -74,7 +74,7 @@ func TestScanner(t *testing.T) {
|
||||
scanner := NewScanner(r, nil)
|
||||
|
||||
if tk := scanner.Next(); tk == nil {
|
||||
t.Errorf("%d: %q -> got = (nil), want %v (value %v [%T)", i+1, input.source, input.wantSym, input.wantValue, input.wantValue)
|
||||
t.Errorf("%d: %q -> got = (nil), want %v (value %v [%T])", i+1, input.source, input.wantSym, input.wantValue, input.wantValue)
|
||||
} else if tk.Sym != input.wantSym || tk.Value != input.wantValue {
|
||||
if tk.Sym == SymError && input.wantSym == tk.Sym {
|
||||
if tkErr, tkOk := tk.Value.(error); tkOk {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||
// All rights reserved.
|
||||
|
||||
// simple-func-store.go
|
||||
package expr
|
||||
|
||||
@@ -32,10 +35,12 @@ func (info *funcInfo) Functor() Functor {
|
||||
}
|
||||
|
||||
func NewSimpleFuncStore() *SimpleFuncStore {
|
||||
return &SimpleFuncStore{
|
||||
ctx := &SimpleFuncStore{
|
||||
SimpleVarStore: SimpleVarStore{varStore: make(map[string]any)},
|
||||
funcStore: make(map[string]*funcInfo),
|
||||
}
|
||||
ImportBuiltinsFuncs(ctx)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) Clone() ExprContext {
|
||||
@@ -45,8 +50,8 @@ func (ctx *SimpleFuncStore) Clone() ExprContext {
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc) {
|
||||
info, _ = ctx.funcStore[name]
|
||||
func (ctx *SimpleFuncStore) GetFuncInfo(name string) (info ExprFunc, exists bool) {
|
||||
info, exists = ctx.funcStore[name]
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+15
-2
@@ -1,6 +1,11 @@
|
||||
// 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
|
||||
}
|
||||
@@ -23,10 +28,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 {
|
||||
@@ -41,7 +54,7 @@ func (ctx *SimpleVarStore) EnumVars(acceptor func(name string) (accept bool)) (v
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx *SimpleVarStore) GetFuncInfo(name string) (f ExprFunc) {
|
||||
func (ctx *SimpleVarStore) GetFuncInfo(name string) (f ExprFunc, exists bool) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,12 @@ const (
|
||||
SymQuestionEqual // 48: '?='
|
||||
SymDoubleAt // 49: '@@'
|
||||
SymDoubleColon // 50: '::'
|
||||
SymInsert // 51: '>>'
|
||||
SymAppend // 52: '<<'
|
||||
SymCaret // 53: '^'
|
||||
SymDollarRound // 54: '$('
|
||||
SymOpenClosedRound // 55: '()'
|
||||
SymDoubleDollar // 56: '$$
|
||||
SymChangeSign
|
||||
SymUnchangeSign
|
||||
SymIdentifier
|
||||
@@ -66,6 +72,7 @@ const (
|
||||
SymInteger
|
||||
SymFloat
|
||||
SymString
|
||||
SymIterator
|
||||
SymOr
|
||||
SymAnd
|
||||
SymNot
|
||||
@@ -73,6 +80,7 @@ const (
|
||||
SymFuncCall
|
||||
SymFuncDef
|
||||
SymList
|
||||
SymDict
|
||||
SymExpression
|
||||
SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
|
||||
SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
|
||||
@@ -87,6 +95,8 @@ const (
|
||||
SymKwOr
|
||||
SymKwBut
|
||||
SymKwFunc
|
||||
SymKwBuiltin
|
||||
SymKwNil
|
||||
)
|
||||
|
||||
var keywords map[string]Symbol
|
||||
@@ -94,10 +104,12 @@ var keywords map[string]Symbol
|
||||
func init() {
|
||||
//keywords = make(map[string]Symbol)
|
||||
keywords = map[string]Symbol{
|
||||
"AND": SymKwAnd,
|
||||
"BUT": SymKwBut,
|
||||
"FUNC": SymKwFunc,
|
||||
"NOT": SymKwNot,
|
||||
"OR": SymKwOr,
|
||||
"AND": SymKwAnd,
|
||||
"BUILTIN": SymKwBuiltin,
|
||||
"BUT": SymKwBut,
|
||||
"FUNC": SymKwFunc,
|
||||
"NOT": SymKwNot,
|
||||
"OR": SymKwOr,
|
||||
"NIL": SymKwNil,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,10 @@ const (
|
||||
priSelector
|
||||
priSign
|
||||
priFact
|
||||
priIterValue
|
||||
priCoalesce
|
||||
priPrePost
|
||||
priDot
|
||||
priValue
|
||||
)
|
||||
|
||||
@@ -126,6 +129,10 @@ func (self *term) setParent(parent *term) {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *term) symbol() Symbol {
|
||||
return self.tk.Sym
|
||||
}
|
||||
|
||||
func (self *term) source() string {
|
||||
return self.tk.source
|
||||
}
|
||||
@@ -143,6 +150,15 @@ func (self *term) compute(ctx ExprContext) (v any, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (self *term) toInt(computedValue any, valueDescription string) (i int, err error) {
|
||||
if index64, ok := computedValue.(int64); ok {
|
||||
i = int(index64)
|
||||
} else {
|
||||
err = self.Errorf("%s, got %T", valueDescription, computedValue)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (self *term) errIncompatibleTypes(leftValue, rightValue any) error {
|
||||
return self.tk.Errorf(
|
||||
"left operand '%v' [%T] and right operand '%v' [%T] are not compatible with operator %q",
|
||||
@@ -166,7 +182,7 @@ func (self *term) checkOperands() (err error) {
|
||||
switch self.position {
|
||||
case posInfix:
|
||||
if self.children == nil || len(self.children) != 2 || self.anyChildrenNil() {
|
||||
err = self.tk.Errorf("infix operator %q requires two not nil operands, got %d", self.source(), self.getChildrenCount())
|
||||
err = self.tk.Errorf("infix operator %q requires two non-nil operands, got %d", self.source(), self.getChildrenCount())
|
||||
}
|
||||
case posPrefix:
|
||||
if self.children == nil || len(self.children) != 1 || self.children[0] == nil {
|
||||
|
||||
+3
-4
@@ -5,7 +5,6 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -16,7 +15,7 @@ func TestString(t *testing.T) {
|
||||
|
||||
tree := NewAst()
|
||||
if gotErr := tree.addTokens(tk1, tk2, tk3); gotErr == nil {
|
||||
fmt.Println("Tree:", tree)
|
||||
t.Log("Tree:", tree)
|
||||
} else {
|
||||
t.Errorf("err: got <%v>, want <nil>", gotErr)
|
||||
}
|
||||
@@ -27,7 +26,7 @@ func TestGetRoom(t *testing.T) {
|
||||
|
||||
tree := NewAst()
|
||||
if gotErr := tree.addTokens(tk1); gotErr == nil {
|
||||
fmt.Println("Tree-root room:", tree.root.getRoom())
|
||||
t.Log("Tree-root room:", tree.root.getRoom())
|
||||
} else {
|
||||
t.Errorf("err: got <%v>, want <nil>", gotErr)
|
||||
}
|
||||
@@ -37,7 +36,7 @@ func TestGetChildrenCount(t *testing.T) {
|
||||
|
||||
tree := NewAst()
|
||||
if gotErr := tree.addTokens(tk1); gotErr == nil {
|
||||
fmt.Println("Tree-root children count:", tree.root.getChildrenCount())
|
||||
t.Log("Tree-root children count:", tree.root.getChildrenCount())
|
||||
} else {
|
||||
t.Errorf("err: got <%v>, want <nil>", gotErr)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
uno
|
||||
due
|
||||
@@ -27,7 +27,11 @@ func (tk *Token) DevString() string {
|
||||
|
||||
func (tk *Token) String() string {
|
||||
if tk.Value != nil {
|
||||
return fmt.Sprintf("%#v", tk.Value)
|
||||
if s, ok := tk.Value.(string); ok {
|
||||
return fmt.Sprintf("%q", s)
|
||||
} else {
|
||||
return fmt.Sprintf("%v", tk.Value)
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s", tk.source)
|
||||
}
|
||||
@@ -63,3 +67,17 @@ 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
|
||||
}
|
||||
|
||||
+25
-4
@@ -10,9 +10,30 @@ import (
|
||||
)
|
||||
|
||||
func TestDevString(t *testing.T) {
|
||||
tk1 := NewValueToken(0, 0, SymInteger, "100", 100)
|
||||
tk2 := NewToken(0, 0, SymPlus, "+")
|
||||
type inputType struct {
|
||||
source string
|
||||
sym Symbol
|
||||
value any
|
||||
wantResult string
|
||||
}
|
||||
|
||||
fmt.Println("Token '100':", tk1.DevString())
|
||||
fmt.Println("Token '+':", tk2.DevString())
|
||||
inputs := []inputType{
|
||||
/* 1 */ {"100", SymInteger, 100, fmt.Sprintf(`[%d]"100"{100}`, SymInteger)},
|
||||
/* 2 */ {"+", SymPlus, nil, fmt.Sprintf(`[%d]"+"{}`, SymPlus)},
|
||||
}
|
||||
|
||||
for i, input := range inputs {
|
||||
var tk *Token
|
||||
if input.value == nil {
|
||||
tk = NewToken(0, 0, input.sym, input.source)
|
||||
} else {
|
||||
tk = NewValueToken(0, 0, input.sym, input.source, input.value)
|
||||
}
|
||||
|
||||
t.Logf("Test nr %2d: %q --> %q", i+1, input.source, input.wantResult)
|
||||
|
||||
if s := tk.DevString(); s != input.wantResult {
|
||||
t.Errorf("wrong token from symbol '+': expected %q, got %q", input.wantResult, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ func isList(v any) (ok bool) {
|
||||
return ok
|
||||
}
|
||||
|
||||
func isDict(v any) (ok bool) {
|
||||
_, ok = v.(map[any]any)
|
||||
return ok
|
||||
}
|
||||
|
||||
func isNumber(v any) (ok bool) {
|
||||
return isFloat(v) || isInteger(v)
|
||||
}
|
||||
@@ -73,6 +78,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 +96,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) {
|
||||
|
||||
Reference in New Issue
Block a user