Skip to content

Commit 941daff

Browse files
committed
WIP: Enhance filter parser
1 parent b297597 commit 941daff

File tree

4 files changed

+348
-75
lines changed

4 files changed

+348
-75
lines changed

internal/filter/contracts.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,8 @@ type Filterable interface {
1313
type Filter interface {
1414
Eval(filterable Filterable) bool
1515
}
16+
17+
type Chainable interface {
18+
Add(rule ...Filter)
19+
Rules() []Filter
20+
}

internal/filter/parser.peg

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
{
2+
package filter
3+
4+
func ParseFilter(expr string, opts ...Option) (Filter, error) {
5+
filter, err := Parse("", []byte(expr), opts...)
6+
if err != nil {
7+
parserErr := err.(errList)[0].(*parserError)
8+
return nil, fmt.Errorf("invalid filter '%s', %s", expr, parserErr.Inner)
9+
}
10+
11+
return filter.(Filter), nil
12+
}
13+
}
14+
15+
Rule <- not:BinaryNot group:LogicalFilterGroup op:LogicalOperator group2:LogicalFilterGroup {
16+
chain, err := NewChain(group2.(Filter), op.(string))
17+
if err != nil {
18+
return nil, err
19+
}
20+
21+
none, err := NewChain(group.(Filter), not.(string))
22+
if err != nil {
23+
return nil, err
24+
}
25+
26+
chain.Add(none.(Filter))
27+
28+
return chain, nil
29+
}
30+
/ not:BinaryNot group:LogicalFilterGroup {
31+
return NewChain(group.(Filter), not.(string))
32+
}
33+
/ group:LogicalFilterGroup op:LogicalOperator not:BinaryNot group2:LogicalFilterGroup {
34+
none, err := NewChain(group2.(Filter), not.(string))
35+
if err != nil {
36+
return nil, err
37+
}
38+
39+
chain, err := NewChain(group.(Filter), op.(string))
40+
if err != nil {
41+
return nil, err
42+
}
43+
44+
chain.Add(none.(Filter))
45+
46+
return chain, nil
47+
}
48+
/ group:LogicalFilterGroup & EOF {
49+
return group, nil
50+
}
51+
LogicalFilterGroup <- open:OpenBrace chain:FilterChain clo:ClosingBrace {
52+
return chain, nil
53+
}
54+
/ chain:FilterChain {
55+
return chain, nil
56+
}
57+
FilterChain <- chain:LogicalConditionExpr {
58+
return chain, nil
59+
}
60+
/ cond:Condition rules:LogicalConditionExpr+ {
61+
filters := rules.([]interface{})
62+
if len(filters) == 0 {
63+
return cond, nil
64+
}
65+
66+
var rule Chainable
67+
chains := make(map[string]Chainable, 1)
68+
for i := len(filters)-1; i >= 0; i-- {
69+
chain := filters[i].(Chainable)
70+
if i == 0 {
71+
chain.Add(cond.(Filter))
72+
}
73+
74+
if i == len(filters)-1 {
75+
rule = chain
76+
continue
77+
}
78+
79+
// We don't need a nested filter chain of the same type!
80+
if reflect.TypeOf(chain) == reflect.TypeOf(rule) {
81+
rule.Add(chain.Rules()...)
82+
continue
83+
}
84+
85+
lastRule, ok := chains[reflect.TypeOf(chain).String()]
86+
if !ok {
87+
chains[reflect.TypeOf(chain).String()] = chain
88+
} else {
89+
lastRule.Add(chain.Rules()...)
90+
}
91+
}
92+
93+
for _, chain := range chains {
94+
rule.Add(chain.(Filter))
95+
}
96+
97+
return rule, nil
98+
}
99+
/ cond:Condition {
100+
return cond, nil
101+
}
102+
LogicalConditionExpr <- op:LogicalOperator not:BinaryNot cond:Condition {
103+
chain, err := NewChain(cond.(Filter), not.(string))
104+
if err != nil {
105+
return nil, err
106+
}
107+
108+
return NewChain(chain.(Filter), op.(string))
109+
}
110+
/ not:BinaryNot cond:Condition {
111+
return NewChain(cond.(Filter), not.(string))
112+
}
113+
/ op:LogicalOperator cond:Condition {
114+
return NewChain(cond.(Filter), op.(string))
115+
}
116+
Condition <- col:Identifier op:Operator val:Identifier {
117+
column, err := url.QueryUnescape(col.(string))
118+
if err != nil {
119+
return nil, err
120+
}
121+
122+
value, err := url.QueryUnescape(val.(string))
123+
if err != nil {
124+
return nil, err
125+
}
126+
127+
return NewCondition(column, op.(string), value)
128+
}
129+
/ expr:ExistsExpr {
130+
return expr, nil
131+
}
132+
ExistsExpr <- col:Identifier &LogicalOperator {
133+
return NewExists(col.(string))
134+
}
135+
/ col:Identifier &EOF {
136+
return NewExists(col.(string))
137+
}
138+
Operator <- ( "<=" / ">=" / "!=" / "=" / "<"/ ">" ) {
139+
c.globalStore["lastMatch"] = "op"
140+
return string(c.text), nil
141+
}
142+
OpenBrace <- open:"(" {
143+
val, ok := c.globalStore["braces"]
144+
if !ok {
145+
c.globalStore["braces"] = 1
146+
} else {
147+
c.globalStore["braces"] = val.(int) + 1
148+
}
149+
150+
return string(c.text), nil
151+
}
152+
ClosingBrace <- clos:")" {
153+
val, ok := c.globalStore["braces"]
154+
if !ok {
155+
c.globalStore["braces"] = -1
156+
} else {
157+
c.globalStore["braces"] = val.(int) - 1
158+
}
159+
160+
return string(c.text), nil
161+
}
162+
BinaryNot <- not:"!" {
163+
c.globalStore["lastMatch"] = "logicalOp"
164+
return string(c.text), nil
165+
}
166+
LogicalOperator <- ( "&" / "|" ) {
167+
c.globalStore["lastMatch"] = "logicalOp"
168+
return string(c.text), nil
169+
}
170+
Identifier "column or value" <- [a-zA-Z0-9_%*]+ {
171+
c.globalStore["lastMatch"] = "identifier"
172+
return string(c.text), nil
173+
}
174+
/ ! {
175+
val, ok := c.globalStore["lastMatch"]
176+
if ok && (val == "op" || val == "logicalOp") {
177+
panic(fmt.Sprintf("unexpected '%s' at pos %d", string(c.text), c.pos.col))
178+
}
179+
180+
braces, ok := c.globalStore["braces"]
181+
if ok && braces.(int) > 0 {
182+
return false, errors.New("missing closing parenthesis ')'")
183+
}
184+
185+
if ok && braces.(int) < 0 {
186+
return false, errors.New("missing opening parenthesis '('")
187+
}
188+
189+
return false, nil
190+
}
191+
EOF <- !.

0 commit comments

Comments
 (0)