glob/match/optimize.go

423 lines
6.8 KiB
Go

package match
import (
"fmt"
"github.com/gobwas/glob/internal/debug"
"github.com/gobwas/glob/util/runes"
)
func Optimize(m Matcher) (opt Matcher) {
if debug.Enabled {
defer func() {
a := fmt.Sprintf("%s", m)
b := fmt.Sprintf("%s", opt)
if a != b {
debug.EnterPrefix("optimized %s: -> %s", a, b)
debug.LeavePrefix()
}
}()
}
switch v := m.(type) {
case Any:
if len(v.sep) == 0 {
return NewSuper()
}
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)
}
case Container:
var (
first Matcher
n int
)
v.Content(func(m Matcher) {
first = m
n++
})
if n == 1 {
return first
}
return m
}
return m
}
func Compile(ms []Matcher) (m Matcher, err error) {
if debug.Enabled {
debug.EnterPrefix("compiling %s", ms)
defer func() {
debug.Logf("-> %s, %v", m, err)
debug.LeavePrefix()
}()
}
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 (
x = -1
max = -2
wantText bool
indexer MatchIndexer
)
for i, m := range ms {
mx, ok := m.(MatchIndexer)
if !ok {
continue
}
_, isText := m.(Text)
if wantText && !isText {
continue
}
n := m.MinLen()
if (!wantText && isText) || n > max {
max = n
x = i
indexer = mx
wantText = isText
}
}
if indexer == nil {
return nil, fmt.Errorf("can not index on matchers")
}
left := ms[:x]
var right []Matcher
if len(ms) > x+1 {
right = ms[x+1:]
}
var (
l Matcher = Nothing{}
r Matcher = Nothing{}
)
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)
}
type result struct {
ms []Matcher
matchers int
maxMinLen int
sumMinLen int
nesting int
}
func compareResult(a, b result) int {
if x := b.sumMinLen - a.sumMinLen; x != 0 {
return x
}
if x := len(a.ms) - len(b.ms); x != 0 {
return x
}
if x := a.nesting - b.nesting; x != 0 {
return x
}
if x := a.matchers - b.matchers; x != 0 {
return x
}
if x := b.maxMinLen - a.maxMinLen; x != 0 {
return x
}
return 0
}
func collapse(ms []Matcher, x Matcher, i, j int) (cp []Matcher) {
cp = make([]Matcher, len(ms)-(j-i)+1)
copy(cp[0:i], ms[0:i])
copy(cp[i+1:], ms[j:])
cp[i] = x
return cp
}
func matchersCount(ms []Matcher) (n int) {
n = len(ms)
for _, m := range ms {
n += countNestedMatchers(m)
}
return n
}
func countNestedMatchers(m Matcher) (n int) {
if c, _ := m.(Container); c != nil {
c.Content(func(m Matcher) {
n += 1 + countNestedMatchers(m)
})
}
return n
}
func nestingDepth(m Matcher) (depth int) {
c, ok := m.(Container)
if !ok {
return 0
}
var max int
c.Content(func(m Matcher) {
if d := nestingDepth(m); d > max {
max = d
}
})
return max + 1
}
func maxMinLen(ms []Matcher) (max int) {
for _, m := range ms {
if n := m.MinLen(); n > max {
max = n
}
}
return max
}
func sumMinLen(ms []Matcher) (sum int) {
for _, m := range ms {
sum += m.MinLen()
}
return sum
}
func maxNestingDepth(ms []Matcher) (max int) {
for _, m := range ms {
if n := nestingDepth(m); n > max {
max = n
}
}
return
}
func minimize(ms []Matcher, i, j int, best *result) *result {
if j > len(ms) {
j = 0
i++
}
if i > len(ms)-2 {
return best
}
if j == 0 {
j = i + 2
}
if g := glueMatchers(ms[i:j]); g != nil {
cp := collapse(ms, g, i, j)
r := result{
ms: cp,
matchers: matchersCount(cp),
sumMinLen: sumMinLen(cp),
maxMinLen: maxMinLen(cp),
nesting: maxNestingDepth(cp),
}
if debug.Enabled {
debug.EnterPrefix(
"intermediate: %s (matchers:%d, summinlen:%d, maxminlen:%d, nesting:%d)",
cp, r.matchers, r.sumMinLen, r.maxMinLen, r.nesting,
)
}
if best == nil {
best = new(result)
}
if best.ms == nil || compareResult(r, *best) < 0 {
*best = r
if debug.Enabled {
debug.Logf("new best result")
}
}
best = minimize(cp, 0, 0, best)
if debug.Enabled {
debug.LeavePrefix()
}
}
return minimize(ms, i, j+1, best)
}
func Minimize(ms []Matcher) (m []Matcher) {
if debug.Enabled {
debug.EnterPrefix("minimizing %s", ms)
defer func() {
debug.Logf("-> %s", m)
debug.LeavePrefix()
}()
}
best := minimize(ms, 0, 0, nil)
if best == nil {
return ms
}
return best.ms
}