mirror of
https://github.com/hanzoai/expr.git
synced 2026-09-24 16:58:58 +00:00
Add type checks
This commits implements more advance type checks in strict mode. To enter strict mode set variable types with expr.Define() or expr.With() functions.
This commit is contained in:
@@ -24,8 +24,8 @@ Inspired by
|
||||
## Features
|
||||
|
||||
* Works with any valid Go object (structs, maps, etc)
|
||||
* Compile-time checks for used variables
|
||||
* Сlear error messages:
|
||||
* Strict mode with type checks
|
||||
* User-friendly error messages
|
||||
```
|
||||
unclosed "("
|
||||
(boo + bar]
|
||||
@@ -48,3 +48,4 @@ go get -u github.com/antonmedv/expr
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
+4
-3
@@ -1,8 +1,9 @@
|
||||
package expr
|
||||
package expr_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/antonmedv/expr"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
@@ -30,13 +31,13 @@ func Benchmark_expr(b *testing.B) {
|
||||
Marker: "test",
|
||||
}
|
||||
|
||||
script, err := Parse(`Segments[0].Origin == "MOW" && Passengers.Adults == 2 && Marker == "test"`)
|
||||
script, err := expr.Parse(`Segments[0].Origin == "MOW" && Passengers.Adults == 2 && Marker == "test"`)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
Run(script, r)
|
||||
expr.Run(script, r)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,10 +42,10 @@ For example you can pass nested structures without any modification or preparati
|
||||
Cookies []Cookie
|
||||
}
|
||||
type Request struct {
|
||||
User user
|
||||
User *user
|
||||
}
|
||||
|
||||
req := Request{User{
|
||||
req := Request{&User{
|
||||
Cookies: []Cookie{{"origin", "www"}},
|
||||
UserAgent: "Firefox",
|
||||
}}
|
||||
@@ -71,39 +71,53 @@ You can also pass functions into the expression:
|
||||
ok, err := expr.Eval(`"www" in Values(Request.User.Cookies)`, data)
|
||||
|
||||
|
||||
Caching
|
||||
Parsing and caching
|
||||
|
||||
If you planning to execute some expression lots times, it's good to compile it first and only one time:
|
||||
If you planning to execute some expression lots times, it's good to parse it first and only one time:
|
||||
|
||||
// Precompile
|
||||
node, err := expr.Parse(expression)
|
||||
// Parse expression to AST.
|
||||
ast, err := expr.Parse(expression)
|
||||
|
||||
// Run
|
||||
ok, err := expr.Run(node, data)
|
||||
// Run given AST
|
||||
ok, err := expr.Run(ast, data)
|
||||
|
||||
|
||||
Checking variables and functions
|
||||
Strict mode
|
||||
|
||||
It is possible to check used variables and functions during parsing of the expression.
|
||||
Expr package support strict parse mode in which some type checks performed during parsing.
|
||||
To parse expression in strict mode, define all of used variables:
|
||||
|
||||
|
||||
expression := `Request.User.UserAgent matches "Firefox" && "www" in Values(Request.User.Cookies)`
|
||||
|
||||
node, err := expr.Parse(expression, expr.Names("Request"), expr.Funcs("Values"))
|
||||
expression := `Request.User.UserAgent matches "Firefox"`
|
||||
node, err := expr.Parse(expression, expr.Define("Request", request{}))
|
||||
|
||||
|
||||
Only `Request` and `Values` will be available inside expression, otherwise parse error.
|
||||
Parse function will check used variables, accessed filed, logical operators and some other type checks.
|
||||
|
||||
If you try to use some undeclared variables or functions, an error will be returned during compilation:
|
||||
If you try to use some undeclared variables, or access unknown field, an error will be returned during paring:
|
||||
|
||||
expression := `Unknown(Request.User.UserAgent)`
|
||||
node, err := expr.Parse(expression, expr.Names("Request"), expr.Funcs("Values"))
|
||||
expression := `Request.User.Cookies[0].Timestamp`
|
||||
node, err := expr.Parse(expression, expr.Define("Request", request{}))
|
||||
|
||||
// err.Error():
|
||||
// err: Request.User.Cookies[0].Timestamp undefined (type expr_test.cookie has no field Timestamp)
|
||||
|
||||
unknown func Unknown
|
||||
Unknown(Request.User.UserAgent)
|
||||
-------^
|
||||
Also it's possible to define all used variables and functions using expr.With and struct:
|
||||
|
||||
type payload struct {
|
||||
Request *Request
|
||||
Values func(xs []Cookie) []string
|
||||
}
|
||||
|
||||
node, err := expr.Parse(expression, expr.With(payload{}))
|
||||
|
||||
Or with map:
|
||||
|
||||
data := map[string]interface{}{
|
||||
"Request": req,
|
||||
"Values": func(xs []Cookie) []string {...},
|
||||
}
|
||||
|
||||
node, err := expr.Parse(expression, expr.With(data))
|
||||
|
||||
|
||||
Printing
|
||||
|
||||
+59
-9
@@ -16,6 +16,7 @@ func ExampleEval() {
|
||||
}
|
||||
|
||||
fmt.Printf("%v", output)
|
||||
|
||||
// Output: hello world
|
||||
}
|
||||
|
||||
@@ -36,6 +37,7 @@ func ExampleEval_map() {
|
||||
}
|
||||
|
||||
fmt.Printf("%v", output)
|
||||
|
||||
// Output: hello user
|
||||
}
|
||||
|
||||
@@ -54,6 +56,7 @@ func ExampleEval_struct() {
|
||||
}
|
||||
|
||||
fmt.Printf("%v", output)
|
||||
|
||||
// Output: 42
|
||||
}
|
||||
|
||||
@@ -66,6 +69,7 @@ func ExampleEval_error() {
|
||||
}
|
||||
|
||||
fmt.Printf("%v", output)
|
||||
|
||||
// Output: err: unclosed "("
|
||||
//(boo + bar]
|
||||
//----------^
|
||||
@@ -80,6 +84,7 @@ func ExampleEval_matches() {
|
||||
}
|
||||
|
||||
fmt.Printf("%v", output)
|
||||
|
||||
// Output: err: error parsing regexp: missing closing ): `a(`
|
||||
//"a" matches "a("
|
||||
//----------------^
|
||||
@@ -106,6 +111,7 @@ func ExampleParse() {
|
||||
}
|
||||
|
||||
fmt.Printf("%v", output)
|
||||
|
||||
// Output: true
|
||||
}
|
||||
|
||||
@@ -130,11 +136,13 @@ func ExampleRun() {
|
||||
}
|
||||
|
||||
fmt.Printf("%v", output)
|
||||
|
||||
// Output: false
|
||||
}
|
||||
|
||||
func ExampleNames() {
|
||||
_, err := expr.Parse("foo + bar + baz", expr.Names("foo", "bar"))
|
||||
func ExampleDefine() {
|
||||
var foo, bar int
|
||||
_, err := expr.Parse("foo + bar + baz", expr.Define("foo", foo), expr.Define("bar", bar))
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("err: %v", err)
|
||||
@@ -142,21 +150,62 @@ func ExampleNames() {
|
||||
}
|
||||
|
||||
// Output: err: unknown name baz
|
||||
//foo + bar + baz
|
||||
//------------^
|
||||
}
|
||||
|
||||
func ExampleFuncs() {
|
||||
_, err := expr.Parse("foo(bar(baz()))", expr.Funcs("foo", "bar"))
|
||||
func ExampleWith() {
|
||||
type segment struct {
|
||||
Origin string
|
||||
}
|
||||
type passengers struct {
|
||||
Adults int
|
||||
}
|
||||
type request struct {
|
||||
Segments []*segment
|
||||
Passengers *passengers
|
||||
Marker string
|
||||
Meta map[string]interface{}
|
||||
}
|
||||
|
||||
code := `Segments[0].Origin == "MOW" && Passengers.Adults == 2 && Marker == "test" && Meta["accept"]`
|
||||
ast, err := expr.Parse(code, expr.With(&request{}))
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Output: err: unknown func baz
|
||||
//foo(bar(baz()))
|
||||
//--------^
|
||||
r := &request{
|
||||
Segments: []*segment{
|
||||
{Origin: "MOW"},
|
||||
},
|
||||
Passengers: &passengers{
|
||||
Adults: 2,
|
||||
},
|
||||
Marker: "test",
|
||||
Meta: map[string]interface{}{"accept": true},
|
||||
}
|
||||
output, err := expr.Run(ast, r)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%v", output)
|
||||
|
||||
// Output: true
|
||||
}
|
||||
|
||||
func ExampleFuncs() {
|
||||
var foo, bar func()
|
||||
_, err := expr.Parse("foo(bar(baz()))", expr.Define("foo", foo), expr.Define("bar", bar))
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Output: err: unknown func baz()
|
||||
}
|
||||
|
||||
func ExampleNode() {
|
||||
@@ -168,5 +217,6 @@ func ExampleNode() {
|
||||
}
|
||||
|
||||
fmt.Printf("%v", node)
|
||||
|
||||
// Output: foo.bar
|
||||
}
|
||||
|
||||
@@ -6,10 +6,6 @@ import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type evaluator interface {
|
||||
eval(env interface{}) (interface{}, error)
|
||||
}
|
||||
|
||||
// Eval parses and evaluates given input.
|
||||
func Eval(input string, env interface{}) (interface{}, error) {
|
||||
node, err := Parse(input)
|
||||
@@ -27,40 +23,37 @@ func Run(node Node, env interface{}) (out interface{}, err error) {
|
||||
}
|
||||
}()
|
||||
|
||||
if e, ok := node.(evaluator); ok {
|
||||
return e.eval(env)
|
||||
}
|
||||
return nil, fmt.Errorf("implement evaluator for %T", node)
|
||||
return node.Eval(env)
|
||||
}
|
||||
|
||||
// eval functions
|
||||
|
||||
func (n nilNode) eval(env interface{}) (interface{}, error) {
|
||||
func (n nilNode) Eval(env interface{}) (interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n identifierNode) eval(env interface{}) (interface{}, error) {
|
||||
func (n identifierNode) Eval(env interface{}) (interface{}, error) {
|
||||
return n.value, nil
|
||||
}
|
||||
|
||||
func (n numberNode) eval(env interface{}) (interface{}, error) {
|
||||
func (n numberNode) Eval(env interface{}) (interface{}, error) {
|
||||
return n.value, nil
|
||||
}
|
||||
|
||||
func (n boolNode) eval(env interface{}) (interface{}, error) {
|
||||
func (n boolNode) Eval(env interface{}) (interface{}, error) {
|
||||
return n.value, nil
|
||||
}
|
||||
|
||||
func (n textNode) eval(env interface{}) (interface{}, error) {
|
||||
func (n textNode) Eval(env interface{}) (interface{}, error) {
|
||||
return n.value, nil
|
||||
}
|
||||
|
||||
func (n nameNode) eval(env interface{}) (interface{}, error) {
|
||||
func (n nameNode) Eval(env interface{}) (interface{}, error) {
|
||||
return extract(env, n.name)
|
||||
}
|
||||
|
||||
func (n unaryNode) eval(env interface{}) (interface{}, error) {
|
||||
val, err := Run(n.node, env)
|
||||
func (n unaryNode) Eval(env interface{}) (interface{}, error) {
|
||||
val, err := n.node.Eval(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -87,8 +80,8 @@ func (n unaryNode) eval(env interface{}) (interface{}, error) {
|
||||
return nil, fmt.Errorf("implement unary %q operator", n.operator)
|
||||
}
|
||||
|
||||
func (n binaryNode) eval(env interface{}) (interface{}, error) {
|
||||
left, err := Run(n.left, env)
|
||||
func (n binaryNode) Eval(env interface{}) (interface{}, error) {
|
||||
left, err := n.left.Eval(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -103,7 +96,7 @@ func (n binaryNode) eval(env interface{}) (interface{}, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
right, err := Run(n.right, env)
|
||||
right, err := n.right.Eval(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -119,7 +112,7 @@ func (n binaryNode) eval(env interface{}) (interface{}, error) {
|
||||
}
|
||||
|
||||
if toBool(left) {
|
||||
right, err := Run(n.right, env)
|
||||
right, err := n.right.Eval(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -132,7 +125,7 @@ func (n binaryNode) eval(env interface{}) (interface{}, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
right, err := Run(n.right, env)
|
||||
right, err := n.right.Eval(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -245,8 +238,8 @@ func makeRange(min, max int64) ([]float64, error) {
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (n matchesNode) eval(env interface{}) (interface{}, error) {
|
||||
left, err := Run(n.left, env)
|
||||
func (n matchesNode) Eval(env interface{}) (interface{}, error) {
|
||||
left, err := n.left.Eval(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -257,7 +250,7 @@ func (n matchesNode) eval(env interface{}) (interface{}, error) {
|
||||
}
|
||||
}
|
||||
|
||||
right, err := Run(n.right, env)
|
||||
right, err := n.right.Eval(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -273,34 +266,41 @@ func (n matchesNode) eval(env interface{}) (interface{}, error) {
|
||||
return nil, fmt.Errorf("operator matches doesn't defined on (%T, %T): %v", left, right, n)
|
||||
}
|
||||
|
||||
func (n propertyNode) eval(env interface{}) (interface{}, error) {
|
||||
v, err := Run(n.node, env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := Run(n.property, env)
|
||||
func (n propertyNode) Eval(env interface{}) (interface{}, error) {
|
||||
v, err := n.node.Eval(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return extract(v, n.property)
|
||||
}
|
||||
|
||||
func (n indexNode) Eval(env interface{}) (interface{}, error) {
|
||||
v, err := n.node.Eval(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := n.index.Eval(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return extract(v, p)
|
||||
}
|
||||
|
||||
func (n methodNode) eval(env interface{}) (interface{}, error) {
|
||||
v, err := Run(n.node, env)
|
||||
func (n methodNode) Eval(env interface{}) (interface{}, error) {
|
||||
v, err := n.node.Eval(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
method, err := extract(v, n.property.value)
|
||||
method, err := extract(v, n.method)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return call(n.property.value, method, n.arguments, env)
|
||||
return call(n.method, method, n.arguments, env)
|
||||
}
|
||||
|
||||
func (n builtinNode) eval(env interface{}) (interface{}, error) {
|
||||
func (n builtinNode) Eval(env interface{}) (interface{}, error) {
|
||||
if len(n.arguments) == 0 {
|
||||
return nil, fmt.Errorf("missing argument to %v", n.name)
|
||||
}
|
||||
@@ -308,7 +308,7 @@ func (n builtinNode) eval(env interface{}) (interface{}, error) {
|
||||
return nil, fmt.Errorf("too many arguments to %v: %v", n.name, n)
|
||||
}
|
||||
|
||||
a, err := Run(n.arguments[0], env)
|
||||
a, err := n.arguments[0].Eval(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -321,7 +321,7 @@ func (n builtinNode) eval(env interface{}) (interface{}, error) {
|
||||
return nil, fmt.Errorf("unknown %q builtin", n.name)
|
||||
}
|
||||
|
||||
func (n functionNode) eval(env interface{}) (interface{}, error) {
|
||||
func (n functionNode) Eval(env interface{}) (interface{}, error) {
|
||||
fn, err := extract(env, n.name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -330,8 +330,8 @@ func (n functionNode) eval(env interface{}) (interface{}, error) {
|
||||
return call(n.name, fn, n.arguments, env)
|
||||
}
|
||||
|
||||
func (n conditionalNode) eval(env interface{}) (interface{}, error) {
|
||||
cond, err := Run(n.cond, env)
|
||||
func (n conditionalNode) Eval(env interface{}) (interface{}, error) {
|
||||
cond, err := n.cond.Eval(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -342,14 +342,14 @@ func (n conditionalNode) eval(env interface{}) (interface{}, error) {
|
||||
}
|
||||
// Then
|
||||
if toBool(cond) {
|
||||
a, err := Run(n.exp1, env)
|
||||
a, err := n.exp1.Eval(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
// Else
|
||||
b, err := Run(n.exp2, env)
|
||||
b, err := n.exp2.Eval(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -357,10 +357,10 @@ func (n conditionalNode) eval(env interface{}) (interface{}, error) {
|
||||
|
||||
}
|
||||
|
||||
func (n arrayNode) eval(env interface{}) (interface{}, error) {
|
||||
func (n arrayNode) Eval(env interface{}) (interface{}, error) {
|
||||
array := make([]interface{}, 0)
|
||||
for _, node := range n.nodes {
|
||||
val, err := Run(node, env)
|
||||
val, err := node.Eval(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -369,14 +369,14 @@ func (n arrayNode) eval(env interface{}) (interface{}, error) {
|
||||
return array, nil
|
||||
}
|
||||
|
||||
func (n mapNode) eval(env interface{}) (interface{}, error) {
|
||||
func (n mapNode) Eval(env interface{}) (interface{}, error) {
|
||||
m := make(map[interface{}]interface{})
|
||||
for _, pair := range n.pairs {
|
||||
key, err := Run(pair.key, env)
|
||||
key, err := pair.key.Eval(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value, err := Run(pair.value, env)
|
||||
value, err := pair.value.Eval(env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+9
-7
@@ -1,10 +1,12 @@
|
||||
package expr
|
||||
package expr_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/antonmedv/expr"
|
||||
)
|
||||
|
||||
type evalTest struct {
|
||||
@@ -413,7 +415,7 @@ var evalErrorTests = []evalErrorTest{
|
||||
|
||||
func TestEval(t *testing.T) {
|
||||
for _, test := range evalTests {
|
||||
actual, err := Eval(test.input, test.env)
|
||||
actual, err := expr.Eval(test.input, test.env)
|
||||
if err != nil {
|
||||
t.Errorf("%s:\n%v", test.input, err)
|
||||
continue
|
||||
@@ -426,7 +428,7 @@ func TestEval(t *testing.T) {
|
||||
|
||||
func TestEval_error(t *testing.T) {
|
||||
for _, test := range evalErrorTests {
|
||||
_, err := Eval(test.input, test.env)
|
||||
_, err := expr.Eval(test.input, test.env)
|
||||
if err == nil {
|
||||
err = fmt.Errorf("<nil>")
|
||||
}
|
||||
@@ -464,12 +466,12 @@ func TestEval_complex(t *testing.T) {
|
||||
}
|
||||
|
||||
input := `Request.User.UserAgent matches "Mozilla" && "www" in Values(Request.User.Cookies)`
|
||||
node, err := Parse(input, Names("Request"), Funcs("Values"))
|
||||
node, err := expr.Parse(input, expr.With(p))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
actual, err := Run(node, p)
|
||||
actual, err := expr.Run(node, p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -481,12 +483,12 @@ func TestEval_complex(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEval_panic(t *testing.T) {
|
||||
node, err := Parse("foo()")
|
||||
node, err := expr.Parse("foo()")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = Run(node, map[string]interface{}{"foo": nil})
|
||||
_, err = expr.Run(node, map[string]interface{}{"foo": nil})
|
||||
if err == nil {
|
||||
err = fmt.Errorf("<nil>")
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package expr
|
||||
|
||||
import "regexp"
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Node represents items of abstract syntax tree.
|
||||
type Node interface{}
|
||||
type Node interface {
|
||||
Type(table typesTable) (Type, error)
|
||||
Eval(env interface{}) (interface{}, error)
|
||||
}
|
||||
|
||||
type nilNode struct{}
|
||||
|
||||
@@ -46,12 +51,17 @@ type matchesNode struct {
|
||||
|
||||
type propertyNode struct {
|
||||
node Node
|
||||
property Node
|
||||
property string
|
||||
}
|
||||
|
||||
type indexNode struct {
|
||||
node Node
|
||||
index Node
|
||||
}
|
||||
|
||||
type methodNode struct {
|
||||
node Node
|
||||
property identifierNode
|
||||
method string
|
||||
arguments []Node
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"unicode/utf8"
|
||||
@@ -62,16 +63,12 @@ type parser struct {
|
||||
tokens []token
|
||||
position int
|
||||
current token
|
||||
options *options
|
||||
}
|
||||
|
||||
type options struct {
|
||||
names map[string]struct{}
|
||||
funcs map[string]struct{}
|
||||
strict bool
|
||||
types map[string]Type
|
||||
}
|
||||
|
||||
// OptionFn for configuring parser.
|
||||
type OptionFn func(p *options)
|
||||
type OptionFn func(p *parser)
|
||||
|
||||
// Parse parses input into ast.
|
||||
func Parse(input string, ops ...OptionFn) (Node, error) {
|
||||
@@ -84,11 +81,11 @@ func Parse(input string, ops ...OptionFn) (Node, error) {
|
||||
input: input,
|
||||
tokens: tokens,
|
||||
current: tokens[0],
|
||||
options: &options{},
|
||||
types: make(map[string]Type),
|
||||
}
|
||||
|
||||
for _, op := range ops {
|
||||
op(p.options)
|
||||
op(p)
|
||||
}
|
||||
|
||||
node, err := p.parseExpression(0)
|
||||
@@ -99,28 +96,53 @@ func Parse(input string, ops ...OptionFn) (Node, error) {
|
||||
if !p.isEOF() {
|
||||
return nil, p.errorf("unexpected token %v", p.current)
|
||||
}
|
||||
|
||||
if p.strict {
|
||||
_, err = node.Type(p.types)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// Names sets list of allowed names.
|
||||
func Names(names ...string) OptionFn {
|
||||
return func(o *options) {
|
||||
set := make(map[string]struct{})
|
||||
for _, name := range names {
|
||||
set[name] = struct{}{}
|
||||
}
|
||||
o.names = set
|
||||
// Define sets variable for type checks during parsing.
|
||||
func Define(name string, t interface{}) OptionFn {
|
||||
return func(p *parser) {
|
||||
p.strict = true
|
||||
p.types[name] = reflect.TypeOf(t)
|
||||
}
|
||||
}
|
||||
|
||||
// Funcs sets list of allowed function.
|
||||
func Funcs(funcs ...string) OptionFn {
|
||||
return func(o *options) {
|
||||
set := make(map[string]struct{})
|
||||
for _, fn := range funcs {
|
||||
set[fn] = struct{}{}
|
||||
// With sets variables for type checks during parsing.
|
||||
// If struct is passed, all fields will be treated as variables.
|
||||
// If map is passed, all items will be treated as variables
|
||||
// (key as name, value as type).
|
||||
func With(i interface{}) OptionFn {
|
||||
return func(p *parser) {
|
||||
p.strict = true
|
||||
v := reflect.ValueOf(i)
|
||||
t := reflect.TypeOf(i)
|
||||
t = dereference(t)
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Struct:
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
p.types[f.Name] = f.Type
|
||||
}
|
||||
case reflect.Map:
|
||||
for _, key := range v.MapKeys() {
|
||||
value := v.MapIndex(key)
|
||||
if key.Kind() == reflect.String && value.IsValid() && value.CanInterface() {
|
||||
p.types[key.String()] = reflect.TypeOf(value.Interface())
|
||||
}
|
||||
}
|
||||
}
|
||||
o.funcs = set
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,32 +321,9 @@ func (p *parser) parsePrimaryExpression() (Node, error) {
|
||||
case "nil":
|
||||
return nilNode{}, nil
|
||||
default:
|
||||
if p.current.is(punctuation, "(") {
|
||||
if _, ok := builtins[token.value]; ok {
|
||||
arguments, err := p.parseArguments()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node = builtinNode{name: token.value, arguments: arguments}
|
||||
} else {
|
||||
if p.options.funcs != nil {
|
||||
if _, ok := p.options.funcs[token.value]; !ok {
|
||||
return nil, p.errorf("unknown func %v", token.value).at(token)
|
||||
}
|
||||
}
|
||||
arguments, err := p.parseArguments()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node = functionNode{name: token.value, arguments: arguments}
|
||||
}
|
||||
} else {
|
||||
if p.options.names != nil {
|
||||
if _, ok := p.options.names[token.value]; !ok {
|
||||
return nil, p.errorf("unknown name %v", token.value).at(token)
|
||||
}
|
||||
}
|
||||
node = nameNode{name: token.value}
|
||||
node, err = p.parseNameExpression(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,6 +362,24 @@ func (p *parser) parsePrimaryExpression() (Node, error) {
|
||||
return p.parsePostfixExpression(node)
|
||||
}
|
||||
|
||||
func (p *parser) parseNameExpression(token token) (Node, error) {
|
||||
var node Node
|
||||
if p.current.is(punctuation, "(") {
|
||||
arguments, err := p.parseArguments()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := builtins[token.value]; ok {
|
||||
node = builtinNode{name: token.value, arguments: arguments}
|
||||
} else {
|
||||
node = functionNode{name: token.value, arguments: arguments}
|
||||
}
|
||||
} else {
|
||||
node = nameNode{name: token.value}
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func (p *parser) parseArrayExpression() (Node, error) {
|
||||
nodes, err := p.parseList("array items", "[", "]")
|
||||
if err != nil {
|
||||
@@ -456,16 +473,14 @@ func (p *parser) parsePostfixExpression(node Node) (Node, error) {
|
||||
return nil, p.errorf("expected name").at(token)
|
||||
}
|
||||
|
||||
property := identifierNode{value: token.value}
|
||||
|
||||
if p.current.is(punctuation, "(") {
|
||||
arguments, err := p.parseArguments()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node = methodNode{node: node, property: property, arguments: arguments}
|
||||
node = methodNode{node: node, method: token.value, arguments: arguments}
|
||||
} else {
|
||||
node = propertyNode{node: node, property: property}
|
||||
node = propertyNode{node: node, property: token.value}
|
||||
}
|
||||
|
||||
} else if token.value == "[" {
|
||||
@@ -476,10 +491,10 @@ func (p *parser) parsePostfixExpression(node Node) (Node, error) {
|
||||
|
||||
arg, err := p.parseExpression(0)
|
||||
if err != nil {
|
||||
return err, nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
node = propertyNode{node: node, property: arg}
|
||||
node = indexNode{node: node, index: arg}
|
||||
|
||||
err = p.expect(punctuation, "]")
|
||||
if err != nil {
|
||||
|
||||
+9
-9
@@ -71,27 +71,27 @@ var parseTests = []parseTest{
|
||||
},
|
||||
{
|
||||
"foo.bar",
|
||||
propertyNode{nameNode{"foo"}, identifierNode{"bar"}},
|
||||
propertyNode{nameNode{"foo"}, "bar"},
|
||||
},
|
||||
{
|
||||
"foo.not",
|
||||
propertyNode{nameNode{"foo"}, identifierNode{"not"}},
|
||||
propertyNode{nameNode{"foo"}, "not"},
|
||||
},
|
||||
{
|
||||
"foo.bar()",
|
||||
methodNode{nameNode{"foo"}, identifierNode{"bar"}, []Node{}},
|
||||
methodNode{nameNode{"foo"}, "bar", []Node{}},
|
||||
},
|
||||
{
|
||||
"foo.not()",
|
||||
methodNode{nameNode{"foo"}, identifierNode{"not"}, []Node{}},
|
||||
methodNode{nameNode{"foo"}, "not", []Node{}},
|
||||
},
|
||||
{
|
||||
`foo.bar("arg1", 2, true)`,
|
||||
methodNode{nameNode{"foo"}, identifierNode{"bar"}, []Node{textNode{"arg1"}, numberNode{2}, boolNode{true}}},
|
||||
methodNode{nameNode{"foo"}, "bar", []Node{textNode{"arg1"}, numberNode{2}, boolNode{true}}},
|
||||
},
|
||||
{
|
||||
"foo[3]",
|
||||
propertyNode{nameNode{"foo"}, numberNode{3}},
|
||||
indexNode{nameNode{"foo"}, numberNode{3}},
|
||||
},
|
||||
{
|
||||
"true ? true : false",
|
||||
@@ -103,7 +103,7 @@ var parseTests = []parseTest{
|
||||
},
|
||||
{
|
||||
"foo.bar().foo().baz[33]",
|
||||
propertyNode{propertyNode{methodNode{methodNode{nameNode{"foo"}, identifierNode{"bar"}, []Node{}}, identifierNode{"foo"}, []Node{}}, identifierNode{"baz"}}, numberNode{33}},
|
||||
indexNode{propertyNode{methodNode{methodNode{nameNode{"foo"}, "bar", []Node{}}, "foo", []Node{}}, "baz"}, numberNode{33}},
|
||||
},
|
||||
{
|
||||
"+0 != -0",
|
||||
@@ -123,11 +123,11 @@ var parseTests = []parseTest{
|
||||
},
|
||||
{
|
||||
"[1].foo",
|
||||
propertyNode{arrayNode{[]Node{numberNode{1}}}, identifierNode{"foo"}},
|
||||
propertyNode{arrayNode{[]Node{numberNode{1}}}, "foo"},
|
||||
},
|
||||
{
|
||||
"{foo:1}.bar",
|
||||
propertyNode{mapNode{[]pairNode{{identifierNode{"foo"}, numberNode{1}}}}, identifierNode{"bar"}},
|
||||
propertyNode{mapNode{[]pairNode{{identifierNode{"foo"}, numberNode{1}}}}, "bar"},
|
||||
},
|
||||
{
|
||||
"len(foo)",
|
||||
|
||||
@@ -49,15 +49,15 @@ func (n matchesNode) String() string {
|
||||
}
|
||||
|
||||
func (n propertyNode) String() string {
|
||||
switch n.property.(type) {
|
||||
case identifierNode:
|
||||
return fmt.Sprintf("%v.%v", n.node, n.property)
|
||||
}
|
||||
return fmt.Sprintf("%v[%v]", n.node, n.property)
|
||||
return fmt.Sprintf("%v.%v", n.node, n.property)
|
||||
}
|
||||
|
||||
func (n indexNode) String() string {
|
||||
return fmt.Sprintf("%v[%v]", n.node, n.index)
|
||||
}
|
||||
|
||||
func (n methodNode) String() string {
|
||||
s := fmt.Sprintf("%v.%v(", n.node, n.property)
|
||||
s := fmt.Sprintf("%v.%v(", n.node, n.method)
|
||||
for i, a := range n.arguments {
|
||||
if i != 0 {
|
||||
s += ", "
|
||||
|
||||
+3
-3
@@ -12,11 +12,11 @@ type printTest struct {
|
||||
|
||||
var printTests = []printTest{
|
||||
{
|
||||
methodNode{nameNode{"foo"}, identifierNode{"bar"}, []Node{textNode{"arg1"}, numberNode{2}, boolNode{true}}},
|
||||
methodNode{nameNode{"foo"}, "bar", []Node{textNode{"arg1"}, numberNode{2}, boolNode{true}}},
|
||||
`foo.bar("arg1", 2, true)`,
|
||||
},
|
||||
{
|
||||
propertyNode{propertyNode{methodNode{methodNode{nameNode{"foo"}, identifierNode{"bar"}, []Node{}}, identifierNode{"foo"}, []Node{}}, identifierNode{"baz"}}, numberNode{33}},
|
||||
indexNode{propertyNode{methodNode{methodNode{nameNode{"foo"}, "bar", []Node{}}, "foo", []Node{}}, "baz"}, numberNode{33}},
|
||||
"foo.bar().foo().baz[33]",
|
||||
},
|
||||
{
|
||||
@@ -24,7 +24,7 @@ var printTests = []printTest{
|
||||
`{"foo": 1, (1 + 2): 2}`,
|
||||
},
|
||||
{
|
||||
functionNode{"call", []Node{propertyNode{arrayNode{[]Node{numberNode{1}, unaryNode{"not", boolNode{true}}}}, identifierNode{"foo"}}}},
|
||||
functionNode{"call", []Node{propertyNode{arrayNode{[]Node{numberNode{1}, unaryNode{"not", boolNode{true}}}}, "foo"}}},
|
||||
"call([1, not true].foo)",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Type is a reflect.Type alias.
|
||||
type Type = reflect.Type
|
||||
|
||||
type typesTable map[string]Type
|
||||
|
||||
var (
|
||||
boolType = reflect.TypeOf(true)
|
||||
numberType = reflect.TypeOf(float64(0))
|
||||
textType = reflect.TypeOf("")
|
||||
arrayType = reflect.TypeOf([]interface{}{})
|
||||
mapType = reflect.TypeOf(map[interface{}]interface{}{})
|
||||
interfaceType = reflect.TypeOf(new(interface{})).Elem()
|
||||
)
|
||||
|
||||
func (n nilNode) Type(table typesTable) (Type, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n identifierNode) Type(table typesTable) (Type, error) {
|
||||
return textType, nil
|
||||
}
|
||||
|
||||
func (n numberNode) Type(table typesTable) (Type, error) {
|
||||
return numberType, nil
|
||||
}
|
||||
|
||||
func (n boolNode) Type(table typesTable) (Type, error) {
|
||||
return boolType, nil
|
||||
}
|
||||
|
||||
func (n textNode) Type(table typesTable) (Type, error) {
|
||||
return textType, nil
|
||||
}
|
||||
|
||||
func (n nameNode) Type(table typesTable) (Type, error) {
|
||||
if t, ok := table[n.name]; ok {
|
||||
return t, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown name %v", n)
|
||||
}
|
||||
|
||||
func (n unaryNode) Type(table typesTable) (Type, error) {
|
||||
ntype, err := n.node.Type(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch n.operator {
|
||||
case "!", "not":
|
||||
if isBoolType(ntype) {
|
||||
return boolType, nil
|
||||
}
|
||||
return nil, fmt.Errorf(`invalid operation: %v (mismatched type %v)`, n, ntype)
|
||||
}
|
||||
|
||||
return interfaceType, nil
|
||||
}
|
||||
|
||||
func (n binaryNode) Type(table typesTable) (Type, error) {
|
||||
var err error
|
||||
ltype, err := n.left.Type(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rtype, err := n.right.Type(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch n.operator {
|
||||
case "==", "!=":
|
||||
if isComparable(ltype, rtype) {
|
||||
return boolType, nil
|
||||
}
|
||||
return nil, fmt.Errorf(`invalid operation: %v (mismatched types %v and %v)`, n, ltype, rtype)
|
||||
case "or", "||", "and", "&&":
|
||||
if isBoolType(ltype) && isBoolType(rtype) {
|
||||
return boolType, nil
|
||||
}
|
||||
return nil, fmt.Errorf(`invalid operation: %v (mismatched types %v and %v)`, n, ltype, rtype)
|
||||
}
|
||||
|
||||
return interfaceType, nil
|
||||
}
|
||||
|
||||
func (n matchesNode) Type(table typesTable) (Type, error) {
|
||||
return boolType, nil
|
||||
}
|
||||
|
||||
func (n propertyNode) Type(table typesTable) (Type, error) {
|
||||
ntype, err := n.node.Type(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t, ok := fieldType(ntype, n.property); ok {
|
||||
return t, nil
|
||||
}
|
||||
return nil, fmt.Errorf("%v undefined (type %v has no field %v)", n, ntype, n.property)
|
||||
}
|
||||
|
||||
func (n indexNode) Type(table typesTable) (Type, error) {
|
||||
ntype, err := n.node.Type(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = n.index.Type(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t, ok := indexType(ntype); ok {
|
||||
return t, nil
|
||||
}
|
||||
return nil, fmt.Errorf("invalid operation: %v (type %v does not support indexing)", n, ntype)
|
||||
}
|
||||
|
||||
func (n methodNode) Type(table typesTable) (Type, error) {
|
||||
ntype, err := n.node.Type(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, node := range n.arguments {
|
||||
_, err := node.Type(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if t, ok := fieldType(ntype, n.method); ok {
|
||||
if f, ok := funcType(t); ok {
|
||||
return f, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%v undefined (type %v has no method %v)", n, ntype, n.method)
|
||||
}
|
||||
|
||||
func (n builtinNode) Type(table typesTable) (Type, error) {
|
||||
if _, ok := builtins[n.name]; ok {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("%v undefined", n)
|
||||
}
|
||||
|
||||
func (n functionNode) Type(table typesTable) (Type, error) {
|
||||
for _, node := range n.arguments {
|
||||
_, err := node.Type(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if t, ok := table[n.name]; ok {
|
||||
if f, ok := funcType(t); ok {
|
||||
return f, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("unknown func %v", n)
|
||||
}
|
||||
|
||||
func (n conditionalNode) Type(table typesTable) (Type, error) {
|
||||
ctype, err := n.cond.Type(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isBoolType(ctype) {
|
||||
return nil, fmt.Errorf("non-bool %v (type %v) used as condition", n.cond, ctype)
|
||||
}
|
||||
_, err = n.exp1.Type(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = n.exp2.Type(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return boolType, nil
|
||||
}
|
||||
|
||||
func (n arrayNode) Type(table typesTable) (Type, error) {
|
||||
for _, node := range n.nodes {
|
||||
_, err := node.Type(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return arrayType, nil
|
||||
}
|
||||
|
||||
func (n mapNode) Type(table typesTable) (Type, error) {
|
||||
for _, node := range n.pairs {
|
||||
_, err := node.Type(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return mapType, nil
|
||||
}
|
||||
|
||||
func (n pairNode) Type(table typesTable) (Type, error) {
|
||||
var err error
|
||||
_, err = n.key.Type(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = n.value.Type(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// helper funcs for reflect
|
||||
|
||||
func isComparable(ltype Type, rtype Type) bool {
|
||||
ltype = dereference(ltype)
|
||||
if ltype == nil {
|
||||
return true
|
||||
}
|
||||
rtype = dereference(rtype)
|
||||
if rtype == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if canBeNumberType(ltype) && canBeNumberType(rtype) {
|
||||
return true
|
||||
} else if ltype.Kind() == reflect.Interface {
|
||||
return true
|
||||
} else if rtype.Kind() == reflect.Interface {
|
||||
return true
|
||||
} else if ltype == rtype {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isBoolType(ntype Type) bool {
|
||||
ntype = dereference(ntype)
|
||||
if ntype == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
switch ntype.Kind() {
|
||||
case reflect.Interface:
|
||||
return true
|
||||
case reflect.Bool:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func fieldType(ntype Type, name string) (Type, bool) {
|
||||
ntype = dereference(ntype)
|
||||
if ntype == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
switch ntype.Kind() {
|
||||
case reflect.Interface:
|
||||
return interfaceType, true
|
||||
case reflect.Struct:
|
||||
if t, ok := ntype.FieldByName(name); ok {
|
||||
return t.Type, true
|
||||
}
|
||||
case reflect.Map:
|
||||
return ntype.Elem(), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func indexType(ntype Type) (Type, bool) {
|
||||
ntype = dereference(ntype)
|
||||
if ntype == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
switch ntype.Kind() {
|
||||
case reflect.Interface:
|
||||
return interfaceType, true
|
||||
case reflect.Map, reflect.Array, reflect.Slice:
|
||||
return ntype.Elem(), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func funcType(ntype Type) (Type, bool) {
|
||||
ntype = dereference(ntype)
|
||||
if ntype == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
switch ntype.Kind() {
|
||||
case reflect.Interface:
|
||||
return interfaceType, true
|
||||
case reflect.Func:
|
||||
return ntype, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func dereference(ntype Type) Type {
|
||||
if ntype == nil {
|
||||
return nil
|
||||
}
|
||||
if ntype.Kind() == reflect.Ptr {
|
||||
ntype = dereference(ntype.Elem())
|
||||
}
|
||||
return ntype
|
||||
}
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
package expr_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/antonmedv/expr"
|
||||
)
|
||||
|
||||
type typeTest string
|
||||
|
||||
type typeErrorTest struct {
|
||||
input string
|
||||
err string
|
||||
}
|
||||
|
||||
var typeTests = []typeTest{
|
||||
"Foo.Bar.Baz",
|
||||
"Arr[0].Bar.Baz",
|
||||
"Map['string'].Bar.Baz",
|
||||
"Map.id.Bar.Baz",
|
||||
"Any.Thing.Is.Ok",
|
||||
"Irr['string'].next.goes['any thing']",
|
||||
"Fn(Any)",
|
||||
"Foo.Fn()",
|
||||
"true ? Any : Any",
|
||||
"len([])",
|
||||
"true == false",
|
||||
"nil",
|
||||
"!Ok",
|
||||
"[1,2,3]",
|
||||
"{id: Foo.Bar.Baz, (1+1): Ok}",
|
||||
"Abc()",
|
||||
"Foo.Abc()",
|
||||
"'a' == 'b' ~ 'c'",
|
||||
"Num == 1",
|
||||
"Int == Num",
|
||||
"Num == Abc",
|
||||
"Abc == Num",
|
||||
"1 == 2 and true or Ok",
|
||||
"IntPtr == Int",
|
||||
"!OkPtr == Ok",
|
||||
"1 == NumPtr",
|
||||
"Foo.Bar == Map.id.Bar",
|
||||
"StrPtr == nil",
|
||||
"nil == nil",
|
||||
"nil == IntPtr",
|
||||
"Foo2p.Bar.Baz",
|
||||
}
|
||||
|
||||
var typeErrorTests = []typeErrorTest{
|
||||
{
|
||||
"Foo.Bar.Not",
|
||||
"Foo.Bar.Not undefined (type expr_test.bar has no field Not)",
|
||||
},
|
||||
{
|
||||
"Noo",
|
||||
"unknown name Noo",
|
||||
},
|
||||
{
|
||||
"Noo()",
|
||||
"unknown func Noo()",
|
||||
},
|
||||
{
|
||||
"Foo()",
|
||||
"unknown func Foo()",
|
||||
},
|
||||
{
|
||||
"Foo['string']",
|
||||
`invalid operation: Foo["string"] (type *expr_test.foo does not support indexing)`,
|
||||
},
|
||||
{
|
||||
"Foo.Fn(Not)",
|
||||
"unknown name Not",
|
||||
},
|
||||
{
|
||||
"Foo.Bar()",
|
||||
"Foo.Bar() undefined (type *expr_test.foo has no method Bar)",
|
||||
},
|
||||
{
|
||||
"Foo.Bar.Not()",
|
||||
"Foo.Bar.Not() undefined (type expr_test.bar has no method Not)",
|
||||
},
|
||||
{
|
||||
"Arr[0].Not",
|
||||
"Arr[0].Not undefined (type *expr_test.foo has no field Not)",
|
||||
},
|
||||
{
|
||||
"Arr[Not]",
|
||||
"unknown name Not",
|
||||
},
|
||||
{
|
||||
"Not[0]",
|
||||
"unknown name Not",
|
||||
},
|
||||
{
|
||||
"Not.Bar",
|
||||
"unknown name Not",
|
||||
},
|
||||
{
|
||||
"Arr.Not",
|
||||
"Arr.Not undefined (type []*expr_test.foo has no field Not)",
|
||||
},
|
||||
{
|
||||
"Fn(Not)",
|
||||
"unknown name Not",
|
||||
},
|
||||
{
|
||||
"Map['str'].Not",
|
||||
`Map["str"].Not undefined (type *expr_test.foo has no field Not)`,
|
||||
},
|
||||
{
|
||||
"No ? Any.Ok : Any.Not",
|
||||
"unknown name No",
|
||||
},
|
||||
{
|
||||
"Any.Cond ? No : Any.Not",
|
||||
"unknown name No",
|
||||
},
|
||||
{
|
||||
"Any.Cond ? Any.Ok : No",
|
||||
"unknown name No",
|
||||
},
|
||||
{
|
||||
"Any ? Any : Any",
|
||||
"non-bool Any (type map[string]interface {}) used as condition",
|
||||
},
|
||||
{
|
||||
"!Not",
|
||||
"unknown name Not",
|
||||
},
|
||||
{
|
||||
"Not == Any",
|
||||
"unknown name Not",
|
||||
},
|
||||
{
|
||||
"[Not]",
|
||||
"unknown name Not",
|
||||
},
|
||||
{
|
||||
"{id: Not}",
|
||||
"unknown name Not",
|
||||
},
|
||||
{
|
||||
"{(Not): Any}",
|
||||
"unknown name Not",
|
||||
},
|
||||
{
|
||||
"(nil).Foo",
|
||||
"nil.Foo undefined (type <nil> has no field Foo)",
|
||||
},
|
||||
{
|
||||
"(nil)['Foo']",
|
||||
`invalid operation: nil["Foo"] (type <nil> does not support indexing)`,
|
||||
},
|
||||
{
|
||||
"1 and false",
|
||||
"invalid operation: (1 and false) (mismatched types float64 and bool)",
|
||||
},
|
||||
{
|
||||
"true or 0",
|
||||
"invalid operation: (true or 0) (mismatched types bool and float64)",
|
||||
},
|
||||
{
|
||||
"not IntPtr",
|
||||
"invalid operation: not IntPtr (mismatched type *int)",
|
||||
},
|
||||
}
|
||||
|
||||
type abc interface {
|
||||
Abc()
|
||||
}
|
||||
type bar struct {
|
||||
Baz string
|
||||
}
|
||||
type foo struct {
|
||||
Bar bar
|
||||
Fn func()
|
||||
Abc abc
|
||||
}
|
||||
type payload struct {
|
||||
Abc abc
|
||||
Foo *foo
|
||||
Arr []*foo
|
||||
Irr []interface{}
|
||||
Map map[string]*foo
|
||||
Any map[string]interface{}
|
||||
Fn func()
|
||||
Ok bool
|
||||
Num float64
|
||||
Int int
|
||||
Str string
|
||||
OkPtr *bool
|
||||
NumPtr *float64
|
||||
IntPtr *int
|
||||
StrPtr *string
|
||||
Foo2p **foo
|
||||
}
|
||||
|
||||
func TestType(t *testing.T) {
|
||||
for _, test := range typeTests {
|
||||
_, err := expr.Parse(string(test), expr.With(&payload{}))
|
||||
if err != nil {
|
||||
t.Errorf("%s:\n\t%+v", test, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestType_error(t *testing.T) {
|
||||
for _, test := range typeErrorTests {
|
||||
_, err := expr.Parse(test.input, expr.With(&payload{}))
|
||||
if err == nil {
|
||||
err = fmt.Errorf("<nil>")
|
||||
}
|
||||
if !strings.HasPrefix(err.Error(), test.err) || test.err == "" {
|
||||
t.Errorf("%s:\ngot\n\t%+v\nexpected\n\t%v", test.input, err.Error(), test.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,14 @@ func cast(v interface{}) (float64, error) {
|
||||
|
||||
func canBeNumber(v interface{}) bool {
|
||||
if v != nil {
|
||||
switch reflect.TypeOf(v).Kind() {
|
||||
return canBeNumberType(reflect.TypeOf(v))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func canBeNumberType(t Type) bool {
|
||||
if t != nil {
|
||||
switch t.Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
fallthrough
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
|
||||
Reference in New Issue
Block a user