Skip to content

Commit f280941

Browse files
author
Codehardt
committed
added normalform, added matchstrings
1 parent 520f0a9 commit f280941

File tree

3 files changed

+248
-2
lines changed

3 files changed

+248
-2
lines changed

normalform.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package evalostic
2+
3+
import (
4+
"fmt"
5+
"sort"
6+
"strings"
7+
)
8+
9+
type matchstr struct {
10+
not bool
11+
ci bool
12+
str string
13+
}
14+
15+
type matchpath []matchstr
16+
17+
func (m matchpath) String() string {
18+
allStrings := make([]string, len(m))
19+
for i, str := range m {
20+
allStrings[i] = str.String()
21+
}
22+
return strings.Join(allStrings, ", ")
23+
}
24+
25+
func (m matchstr) String() string {
26+
var suffix string
27+
if m.ci {
28+
suffix = "i"
29+
}
30+
var prefix string
31+
if m.not {
32+
prefix = "NOT "
33+
}
34+
return fmt.Sprintf("%s%q%s", prefix, m.str, suffix)
35+
}
36+
37+
func MatchStrings(n node) []matchpath {
38+
res := matchStrings(n)
39+
for _, path := range res {
40+
sort.Slice(path, func(i, j int) bool {
41+
s1, s2 := path[i], path[j]
42+
if s1.not && !s2.not {
43+
return false
44+
}
45+
if !s1.not && s2.not {
46+
return true
47+
}
48+
return strings.Compare(s1.str, s2.str) < 0
49+
})
50+
}
51+
return res
52+
}
53+
54+
func matchStrings(n node) []matchpath {
55+
switch v := n.(type) {
56+
case nodeAND:
57+
return []matchpath{append(matchStrings(v.node1)[0], matchStrings(v.node2)[0]...)}
58+
case nodeOR:
59+
return append(matchStrings(v.node1), matchStrings(v.node2)...)
60+
case nodeNOT:
61+
val := v.node.(nodeVAL)
62+
return []matchpath{
63+
{matchstr{
64+
not: true,
65+
ci: val.caseInsensitive,
66+
str: val.nodeValue,
67+
}},
68+
}
69+
case nodeVAL:
70+
return []matchpath{
71+
{matchstr{
72+
not: false,
73+
ci: v.caseInsensitive,
74+
str: v.nodeValue,
75+
}},
76+
}
77+
default:
78+
panic("unknown node type")
79+
}
80+
}
81+
82+
func (n nodeAND) NormalForm() node {
83+
n.node1 = n.node1.NormalForm()
84+
if or, ok := n.node1.(nodeOR); ok {
85+
return (nodeOR{
86+
twoSubNodes{
87+
node1: nodeAND{
88+
twoSubNodes{
89+
node1: or.node1,
90+
node2: n.node2,
91+
},
92+
},
93+
node2: nodeAND{
94+
twoSubNodes{
95+
node1: or.node2,
96+
node2: n.node2,
97+
},
98+
},
99+
},
100+
}).NormalForm()
101+
}
102+
n.node2 = n.node2.NormalForm()
103+
if or, ok := n.node2.(nodeOR); ok {
104+
return (nodeOR{
105+
twoSubNodes{
106+
node1: nodeAND{
107+
twoSubNodes{
108+
node1: n.node1,
109+
node2: or.node1,
110+
},
111+
},
112+
node2: nodeAND{
113+
twoSubNodes{
114+
node1: n.node1,
115+
node2: or.node2,
116+
},
117+
},
118+
},
119+
}).NormalForm()
120+
}
121+
return n
122+
}
123+
124+
func (n nodeOR) NormalForm() node {
125+
n.node1 = n.node1.NormalForm()
126+
n.node2 = n.node2.NormalForm()
127+
return n
128+
}
129+
130+
func (n nodeNOT) NormalForm() node {
131+
n.node = n.node.NormalForm()
132+
switch v := n.node.(type) {
133+
case nodeAND:
134+
return (nodeOR{
135+
twoSubNodes{
136+
node1: nodeNOT{
137+
oneSubNode{
138+
node: v.node1,
139+
},
140+
},
141+
node2: nodeNOT{
142+
oneSubNode{
143+
node: v.node2,
144+
},
145+
},
146+
},
147+
}).NormalForm()
148+
case nodeOR:
149+
return (nodeAND{
150+
twoSubNodes{
151+
node1: nodeNOT{
152+
oneSubNode{
153+
node: v.node1,
154+
},
155+
},
156+
node2: nodeNOT{
157+
oneSubNode{
158+
node: v.node2,
159+
},
160+
},
161+
},
162+
}).NormalForm()
163+
case nodeVAL:
164+
return n
165+
case nodeNOT:
166+
return v.node
167+
default:
168+
panic("unknown node type")
169+
}
170+
}
171+
172+
func (n nodeVAL) NormalForm() node {
173+
return n
174+
}

normalform_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package evalostic
2+
3+
import "fmt"
4+
5+
func ExampleNormalForm() {
6+
nf := func(cond string) {
7+
n, err := parseCondition(cond)
8+
if err != nil {
9+
panic(err)
10+
}
11+
fmt.Printf("----- %s -----\n", cond)
12+
fmt.Println("before:", n.Condition())
13+
n = n.NormalForm()
14+
fmt.Println("after:", n.Condition())
15+
}
16+
nf(`"a"`)
17+
nf(`NOT "a"`)
18+
nf(`"a" AND "b"`)
19+
nf(`"a" OR "b"`)
20+
nf(`"a" AND ("b" OR "c")`)
21+
nf(`"a" OR ("b" AND "c")`)
22+
nf(`"a" AND NOT ("b" OR "c")`)
23+
nf(`"a" OR NOT ("b" AND "c")`)
24+
nf(`"a" AND ("b" OR NOT "c")`)
25+
nf(`"a" OR ("b" AND NOT "c")`)
26+
nf(`"a" AND NOT ("b" OR NOT "c")`)
27+
nf(`"a" OR NOT ("b" AND NOT "c")`)
28+
nf(`"a" OR ("b" OR ("c" OR "d"))`)
29+
nf(`("a" OR "b") OR ("c" OR "d")`)
30+
// Output:
31+
//
32+
}
33+
34+
func ExampleMatchStrings() {
35+
ms := func(cond string) {
36+
n, err := parseCondition(cond)
37+
if err != nil {
38+
panic(err)
39+
}
40+
fmt.Printf("----- %s -----\n", cond)
41+
n = n.NormalForm()
42+
matchStrings := MatchStrings(n)
43+
for _, matchPath := range matchStrings {
44+
fmt.Println(matchPath.String())
45+
}
46+
}
47+
ms(`("a" AND NOT "b") OR (NOT "c" AND "d")`)
48+
// Output:
49+
//
50+
}

parser.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ package evalostic
33
import (
44
"errors"
55
"fmt"
6+
"strconv"
67
"strings"
78
)
89

910
type node interface {
1011
String() string
12+
Condition() string
1113
Value() string
1214
Children() (node, node)
15+
NormalForm() node
1316
}
1417

1518
type (
@@ -69,11 +72,11 @@ func parse(tokens []token) (node, error) {
6972
)
7073
for {
7174
if offset >= len(tokens)-1 {
72-
return nil, errors.New("missing matching closing parentheses")
75+
return nil, errors.New("missing matching closing parentheses 1")
7376
}
7477
rPos = findToken(tokens[offset:], tokenTypeRPAR)
7578
if rPos < 0 {
76-
return nil, errors.New("missing matching closing parentheses")
79+
return nil, errors.New("missing matching closing parentheses 2")
7780
}
7881
rPos += offset
7982
if lPos+1 == rPos {
@@ -158,3 +161,22 @@ func parse(tokens []token) (node, error) {
158161
}
159162
return startNode, nil
160163
}
164+
165+
func (n nodeAND) Condition() string {
166+
return fmt.Sprintf("(%s AND %s)", n.node1.Condition(), n.node2.Condition())
167+
}
168+
169+
func (n nodeOR) Condition() string {
170+
return fmt.Sprintf("(%s OR %s)", n.node1.Condition(), n.node2.Condition())
171+
}
172+
173+
func (n nodeVAL) Condition() string {
174+
if n.caseInsensitive {
175+
return strconv.Quote(n.nodeValue) + "i"
176+
}
177+
return strconv.Quote(n.nodeValue)
178+
}
179+
180+
func (n nodeNOT) Condition() string {
181+
return fmt.Sprintf("NOT %s", n.node.Condition())
182+
}

0 commit comments

Comments
 (0)