Skip to content

Commit

Permalink
WIP: Enhance filter parser
Browse files Browse the repository at this point in the history
  • Loading branch information
yhabteab committed Apr 28, 2023
1 parent b297597 commit 941daff
Show file tree
Hide file tree
Showing 4 changed files with 348 additions and 75 deletions.
5 changes: 5 additions & 0 deletions internal/filter/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ type Filterable interface {
type Filter interface {
Eval(filterable Filterable) bool
}

type Chainable interface {
Add(rule ...Filter)
Rules() []Filter
}
191 changes: 191 additions & 0 deletions internal/filter/parser.peg
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
{
package filter

func ParseFilter(expr string, opts ...Option) (Filter, error) {
filter, err := Parse("", []byte(expr), opts...)
if err != nil {
parserErr := err.(errList)[0].(*parserError)
return nil, fmt.Errorf("invalid filter '%s', %s", expr, parserErr.Inner)
}

return filter.(Filter), nil
}
}

Rule <- not:BinaryNot group:LogicalFilterGroup op:LogicalOperator group2:LogicalFilterGroup {
chain, err := NewChain(group2.(Filter), op.(string))
if err != nil {
return nil, err
}

none, err := NewChain(group.(Filter), not.(string))
if err != nil {
return nil, err
}

chain.Add(none.(Filter))

return chain, nil
}
/ not:BinaryNot group:LogicalFilterGroup {
return NewChain(group.(Filter), not.(string))
}
/ group:LogicalFilterGroup op:LogicalOperator not:BinaryNot group2:LogicalFilterGroup {
none, err := NewChain(group2.(Filter), not.(string))
if err != nil {
return nil, err
}

chain, err := NewChain(group.(Filter), op.(string))
if err != nil {
return nil, err
}

chain.Add(none.(Filter))

return chain, nil
}
/ group:LogicalFilterGroup & EOF {
return group, nil
}
LogicalFilterGroup <- open:OpenBrace chain:FilterChain clo:ClosingBrace {
return chain, nil
}
/ chain:FilterChain {
return chain, nil
}
FilterChain <- chain:LogicalConditionExpr {
return chain, nil
}
/ cond:Condition rules:LogicalConditionExpr+ {
filters := rules.([]interface{})
if len(filters) == 0 {
return cond, nil
}

var rule Chainable
chains := make(map[string]Chainable, 1)
for i := len(filters)-1; i >= 0; i-- {
chain := filters[i].(Chainable)
if i == 0 {
chain.Add(cond.(Filter))
}

if i == len(filters)-1 {
rule = chain
continue
}

// We don't need a nested filter chain of the same type!
if reflect.TypeOf(chain) == reflect.TypeOf(rule) {
rule.Add(chain.Rules()...)
continue
}

lastRule, ok := chains[reflect.TypeOf(chain).String()]
if !ok {
chains[reflect.TypeOf(chain).String()] = chain
} else {
lastRule.Add(chain.Rules()...)
}
}

for _, chain := range chains {
rule.Add(chain.(Filter))
}

return rule, nil
}
/ cond:Condition {
return cond, nil
}
LogicalConditionExpr <- op:LogicalOperator not:BinaryNot cond:Condition {
chain, err := NewChain(cond.(Filter), not.(string))
if err != nil {
return nil, err
}

return NewChain(chain.(Filter), op.(string))
}
/ not:BinaryNot cond:Condition {
return NewChain(cond.(Filter), not.(string))
}
/ op:LogicalOperator cond:Condition {
return NewChain(cond.(Filter), op.(string))
}
Condition <- col:Identifier op:Operator val:Identifier {
column, err := url.QueryUnescape(col.(string))
if err != nil {
return nil, err
}

value, err := url.QueryUnescape(val.(string))
if err != nil {
return nil, err
}

return NewCondition(column, op.(string), value)
}
/ expr:ExistsExpr {
return expr, nil
}
ExistsExpr <- col:Identifier &LogicalOperator {
return NewExists(col.(string))
}
/ col:Identifier &EOF {
return NewExists(col.(string))
}
Operator <- ( "<=" / ">=" / "!=" / "=" / "<"/ ">" ) {
c.globalStore["lastMatch"] = "op"
return string(c.text), nil
}
OpenBrace <- open:"(" {
val, ok := c.globalStore["braces"]
if !ok {
c.globalStore["braces"] = 1
} else {
c.globalStore["braces"] = val.(int) + 1
}

return string(c.text), nil
}
ClosingBrace <- clos:")" {
val, ok := c.globalStore["braces"]
if !ok {
c.globalStore["braces"] = -1
} else {
c.globalStore["braces"] = val.(int) - 1
}

return string(c.text), nil
}
BinaryNot <- not:"!" {
c.globalStore["lastMatch"] = "logicalOp"
return string(c.text), nil
}
LogicalOperator <- ( "&" / "|" ) {
c.globalStore["lastMatch"] = "logicalOp"
return string(c.text), nil
}
Identifier "column or value" <- [a-zA-Z0-9_%*]+ {
c.globalStore["lastMatch"] = "identifier"
return string(c.text), nil
}
/ ! {
val, ok := c.globalStore["lastMatch"]
if ok && (val == "op" || val == "logicalOp") {
panic(fmt.Sprintf("unexpected '%s' at pos %d", string(c.text), c.pos.col))
}

braces, ok := c.globalStore["braces"]
if ok && braces.(int) > 0 {
return false, errors.New("missing closing parenthesis ')'")
}

if ok && braces.(int) < 0 {
return false, errors.New("missing opening parenthesis '('")
}

return false, nil
}
EOF <- !.
Loading

0 comments on commit 941daff

Please sign in to comment.