Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ type SliceNumberLiteral struct {

// String returns a string representation of the literal.
func (l *SliceNumberLiteral) String() string {
return fmt.Sprintf("%s", l.Val)
return fmt.Sprintf("%v", l.Val)
}

func (l *SliceNumberLiteral) Args() []string {
Expand Down
61 changes: 60 additions & 1 deletion evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package conditions

import (
"fmt"
"github.com/pkg/errors"
"reflect"
"regexp"
)
Expand Down Expand Up @@ -78,7 +79,18 @@ func evaluateSubtree(expr Expr, args map[string]interface{}) (Expr, error) {
case reflect.Bool:
return &BooleanLiteral{Val: args[index].(bool)}, nil
case reflect.Slice:
return &SliceStringLiteral{Val: args[index].([]string)}, nil
stringsSlice := []string{}
if slice, ok := args[index].([]interface{}); ok {
for _, value := range slice {
if s, ok := value.(string); ok {
stringsSlice = append(stringsSlice, s)
}
}
return &SliceStringLiteral{Val: stringsSlice}, nil
}
if _, ok := args[index].([]string); ok {
return &SliceStringLiteral{Val: args[index].([]string)}, nil
}
}
return falseExpr, fmt.Errorf("Unsupported argument %s type: %s", n.Val, kind)
}
Expand Down Expand Up @@ -117,10 +129,57 @@ func applyOperator(op Token, l, r Expr) (*BooleanLiteral, error) {
return applyEREG(l, r)
case NEREG:
return applyNEREG(l, r)
case INTERSECTS:
return applyINTERSECTS(l, r)
case HAS:
return applyHAS(l, r)
}
return &BooleanLiteral{Val: false}, fmt.Errorf("Unsupported operator: %s", op)
}

// applyINTERSECTS return true if intersect of two sets is not empty (todo: extend for number slices, upgrade for case insensitive)
func applyINTERSECTS(l, r Expr) (*BooleanLiteral, error) {
left, err := getSliceString(l)
if err != nil {
return &BooleanLiteral{Val: false}, errors.Wrap(err, "left operand of INTERSECTS operator must have string slice type")
}

right, err := getSliceString(r)
if err != nil {
return &BooleanLiteral{Val: false}, errors.Wrap(err, "right operand of INTERSECTS operator must have string slice type")
}

for _, val := range left {
for _, rightVal := range right {
if val == rightVal {
// If at least one element found at right set - intersection already is not empty
return &BooleanLiteral{Val: true}, nil
}
}
}
return &BooleanLiteral{Val: false}, nil
}

// applyHAS applies HAS operation to l/r operands
func applyHAS(l, r Expr) (*BooleanLiteral, error) {
left, err := getSliceString(l)
if err != nil {
return &BooleanLiteral{Val: false}, errors.Wrap(err, "left operand of HAS operator must have string slice type")
}

right, err := getString(r)
if err != nil {
return &BooleanLiteral{Val: false}, errors.Wrap(err, "right operand of HAS operator must be a string")
}

for _, val := range left {
if val == right {
return &BooleanLiteral{Val: true}, nil
}
}
return &BooleanLiteral{Val: false}, nil
}

// applyEREG applies EREG operation to l/r operands
func applyNEREG(l, r Expr) (*BooleanLiteral, error) {
result, err := applyEREG(l, r)
Expand Down
4 changes: 4 additions & 0 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ func (p *Parser) scanWithMapping() (Token, string) {
tok = NAND
} else if ttU == "IN" {
tok = IN
} else if ttU == "INTERSECTS" {
tok = INTERSECTS
} else if ttU == "HAS" {
tok = HAS
} else if ttU == "NOT" {
_, tmp := p.scan()
if strings.ToUpper(tmp) == "IN" {
Expand Down
18 changes: 14 additions & 4 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ var validTestData = []struct {
{"[var0]", map[string]interface{}{"var0": true}, true, false},
{"[var0]", map[string]interface{}{"var0": false}, false, false},
{"\"OFF\"", nil, false, true},
{"`ON`", nil, false, true},
//{"`ON`", nil, false, true},
{"[var0] == \"OFF\"", map[string]interface{}{"var0": "OFF"}, true, false},
{"[var0] > 10 AND [var1] == \"OFF\"", map[string]interface{}{"var0": 14, "var1": "OFF"}, true, false},
{"([var0] > 10) AND ([var1] == \"OFF\")", map[string]interface{}{"var0": 14, "var1": "OFF"}, true, false},
Expand Down Expand Up @@ -93,6 +93,18 @@ var validTestData = []struct {
// !~
{"[status] !~ /^5\\d\\d/", map[string]interface{}{"status": "500"}, false, false},
{"[status] !~ /^4\\d\\d/", map[string]interface{}{"status": "500"}, true, false},

{`[foo] HAS "5"`, map[string]interface{}{"foo": []string{"5", "3"}}, true, false},
{`[foo] HAS "4"`, map[string]interface{}{"foo": []string{"5", "3"}}, false, false},
{`[foo] HAS ["4"]`, map[string]interface{}{"foo": []string{"5", "3"}}, false, true},
{`[foo] HAS 3`, map[string]interface{}{"foo": []string{"5", "3"}}, false, true},

{`[foo] INTERSECTS ["5", "7"]`, map[string]interface{}{"foo": []string{"5", "3"}}, true, false},
{`[foo] INTERSECTS ["4", "8"]`, map[string]interface{}{"foo": []string{"5", "3"}}, false, false},
{`[foo] INTERSECTS [5, 3]`, map[string]interface{}{"foo": []string{"5", "3"}}, false, true},
{`[foo] INTERSECTS "4"`, map[string]interface{}{"foo": []string{"5", "3"}}, false, true},
{`[foo] INTERSECTS ["5", "7"]`, map[string]interface{}{"foo": "4"}, false, true},
{`[foo] INTERSECTS ["5", "7"]`, map[string]interface{}{"foo": []int{5, 7}}, false, true},
}

func TestInvalid(t *testing.T) {
Expand Down Expand Up @@ -139,14 +151,12 @@ func TestValid(t *testing.T) {
t.Error(err.Error())
break
}

t.Log("Evaluating with: %#v", td.args)
r, err = Evaluate(expr, td.args)
if err != nil {
if td.isErr {
continue
}
t.Errorf("Unexpected error evaluating: %s", expr)
t.Errorf("Unexpected error evaluating: %v", expr)
t.Error(err.Error())
break
}
Expand Down
35 changes: 20 additions & 15 deletions token.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,22 @@ const (
literalEnd

operatorBegin
AND // AND
OR // OR
EQ // =
NEQ // !=
LT // <
LTE // <=
GT // >
GTE // >=
NAND // NAND
XOR // XOR
EREG // =~
NEREG // !~
IN // IN
NOTIN // NOT IN
AND // AND
OR // OR
EQ // =
NEQ // !=
LT // <
LTE // <=
GT // >
GTE // >=
NAND // NAND
XOR // XOR
EREG // =~
NEREG // !~
IN // IN
NOTIN // NOT IN
INTERSECTS // INTERSECTS (two sets has not empty intersect)
HAS // HAS (element contains at set as left operand)
operatorEnd

LPAREN // (
Expand Down Expand Up @@ -69,6 +71,9 @@ var tokens = [...]string{

LPAREN: "(",
RPAREN: ")",

INTERSECTS: "INTERSECTS",
HAS: "HAS",
}

// String returns the string representation of the token.
Expand All @@ -87,7 +92,7 @@ func (tok Token) Precedence() int {
case AND, NAND:
return 2

case EQ, NEQ, LT, LTE, GT, GTE, IN, NOTIN, EREG, NEREG:
case EQ, NEQ, LT, LTE, GT, GTE, IN, NOTIN, EREG, NEREG, INTERSECTS, HAS:
return 3
}
return 0
Expand Down