glob/match/optimize.go

279 lines
4.3 KiB
Go
Raw Normal View History

2018-02-16 17:36:02 +03:00
package match
import (
"fmt"
2018-10-02 22:02:43 +03:00
"github.com/gobwas/glob/util/runes"
2018-02-16 17:36:02 +03:00
)
func Optimize(m Matcher) Matcher {
switch v := m.(type) {
case Any:
if len(v.sep) == 0 {
return NewSuper()
}
case Container:
ms := v.Content()
if len(ms) == 1 {
return ms[0]
}
return m
case List:
if v.not == false && len(v.rs) == 1 {
return NewText(string(v.rs))
}
return m
case Tree:
v.left = Optimize(v.left)
v.right = Optimize(v.right)
txt, ok := v.value.(Text)
if !ok {
return m
}
var (
leftNil = v.left == nil
rightNil = v.right == nil
)
if leftNil && rightNil {
return NewText(txt.s)
}
_, leftSuper := v.left.(Super)
lp, leftPrefix := v.left.(Prefix)
la, leftAny := v.left.(Any)
_, rightSuper := v.right.(Super)
rs, rightSuffix := v.right.(Suffix)
ra, rightAny := v.right.(Any)
switch {
case leftSuper && rightSuper:
return NewContains(txt.s)
case leftSuper && rightNil:
return NewSuffix(txt.s)
case rightSuper && leftNil:
return NewPrefix(txt.s)
case leftNil && rightSuffix:
return NewPrefixSuffix(txt.s, rs.s)
case rightNil && leftPrefix:
return NewPrefixSuffix(lp.s, txt.s)
case rightNil && leftAny:
return NewSuffixAny(txt.s, la.sep)
case leftNil && rightAny:
return NewPrefixAny(txt.s, ra.sep)
}
}
return m
}
func Compile(ms []Matcher) (Matcher, error) {
if len(ms) == 0 {
return nil, fmt.Errorf("compile error: need at least one matcher")
}
if len(ms) == 1 {
return ms[0], nil
}
if m := glueMatchers(ms); m != nil {
return m, nil
}
var (
idx = -1
maxLen = -2
indexer MatchIndexer
)
for i, m := range ms {
mi, ok := m.(MatchIndexer)
if !ok {
continue
}
if n := m.MinLen(); n > maxLen {
maxLen = n
idx = i
indexer = mi
}
}
if indexer == nil {
return nil, fmt.Errorf("can not index on matchers")
}
left := ms[:idx]
var right []Matcher
if len(ms) > idx+1 {
right = ms[idx+1:]
}
var l, r Matcher
var err error
if len(left) > 0 {
l, err = Compile(left)
if err != nil {
return nil, err
}
}
if len(right) > 0 {
r, err = Compile(right)
if err != nil {
return nil, err
}
}
return NewTree(indexer, l, r), nil
}
func glueMatchers(ms []Matcher) Matcher {
if m := glueMatchersAsEvery(ms); m != nil {
return m
}
if m := glueMatchersAsRow(ms); m != nil {
return m
}
return nil
}
func glueMatchersAsRow(ms []Matcher) Matcher {
if len(ms) <= 1 {
return nil
}
var s []MatchIndexSizer
for _, m := range ms {
rsz, ok := m.(MatchIndexSizer)
if !ok {
return nil
}
s = append(s, rsz)
}
return NewRow(s)
}
func glueMatchersAsEvery(ms []Matcher) Matcher {
if len(ms) <= 1 {
return nil
}
var (
hasAny bool
hasSuper bool
hasSingle bool
min int
separator []rune
)
for i, matcher := range ms {
var sep []rune
switch m := matcher.(type) {
case Super:
sep = []rune{}
hasSuper = true
case Any:
sep = m.sep
hasAny = true
case Single:
sep = m.sep
hasSingle = true
min++
case List:
if !m.not {
return nil
}
sep = m.rs
hasSingle = true
min++
default:
return nil
}
// initialize
if i == 0 {
separator = sep
}
if runes.Equal(sep, separator) {
continue
}
return nil
}
if hasSuper && !hasAny && !hasSingle {
return NewSuper()
}
if hasAny && !hasSuper && !hasSingle {
return NewAny(separator)
}
if (hasAny || hasSuper) && min > 0 && len(separator) == 0 {
return NewMin(min)
}
var every []Matcher
if min > 0 {
every = append(every, NewMin(min))
if !hasAny && !hasSuper {
every = append(every, NewMax(min))
}
}
if len(separator) > 0 {
every = append(every, NewAny(separator))
}
return NewEveryOf(every)
}
func Minimize(ms []Matcher) []Matcher {
var (
result Matcher
left int
right int
count int
)
for l := 0; l < len(ms); l++ {
for r := len(ms); r > l; r-- {
if glued := glueMatchers(ms[l:r]); glued != nil {
var swap bool
if result == nil {
swap = true
} else {
swap = glued.MinLen() > result.MinLen() || count < r-l
}
if swap {
result = glued
left = l
right = r
count = r - l
}
}
}
}
if result == nil {
return ms
}
next := append(append([]Matcher{}, ms[:left]...), result)
if right < len(ms) {
next = append(next, ms[right:]...)
}
if len(next) == len(ms) {
return next
}
return Minimize(next)
}