glob/compiler.go

259 lines
4.0 KiB
Go
Raw Normal View History

2016-01-08 20:14:31 +03:00
package glob
import (
"fmt"
"github.com/gobwas/glob/match"
)
func optimize(matcher match.Matcher) match.Matcher {
switch m := matcher.(type) {
case match.Any:
if m.Separators == "" {
return match.Super{}
}
case match.BTree:
m.Left = optimize(m.Left)
m.Right = optimize(m.Right)
r, ok := m.Value.(match.Raw)
if !ok {
return m
}
leftNil := m.Left == nil
rightNil := m.Right == nil
if leftNil && rightNil {
return match.Raw{r.Str}
}
_, leftSuper := m.Left.(match.Super)
lp, leftPrefix := m.Left.(match.Prefix)
_, rightSuper := m.Right.(match.Super)
rs, rightSuffix := m.Right.(match.Suffix)
if leftSuper && rightSuper {
return match.Contains{r.Str, false}
}
if leftSuper && rightNil {
return match.Suffix{r.Str}
}
if rightSuper && leftNil {
return match.Prefix{r.Str}
}
if leftNil && rightSuffix {
return match.Every{match.Matchers{match.Prefix{r.Str}, rs}}
}
if rightNil && leftPrefix {
return match.Every{match.Matchers{lp, match.Suffix{r.Str}}}
}
return m
}
return matcher
}
func glueMatchers(matchers []match.Matcher) match.Matcher {
switch len(matchers) {
case 0:
return nil
case 1:
return matchers[0]
}
var (
hasAny bool
hasSuper bool
hasSingle bool
min int
separator string
)
for i, matcher := range matchers {
var sep string
switch m := matcher.(type) {
case match.Super:
sep = ""
hasSuper = true
case match.Any:
sep = m.Separators
hasAny = true
case match.Single:
sep = m.Separators
hasSingle = true
min++
case match.List:
if !m.Not {
return nil
}
sep = m.List
hasSingle = true
min++
default:
return nil
}
// initialize
if i == 0 {
separator = sep
}
if sep == separator {
continue
}
return nil
}
if hasSuper && !hasAny && !hasSingle {
return match.Super{}
}
if hasAny && !hasSuper && !hasSingle {
return match.Any{separator}
}
if (hasAny || hasSuper) && min > 0 && separator == "" {
return match.Min{min}
}
every := match.Every{}
if min > 0 {
every.Add(match.Min{min})
if !hasAny && !hasSuper {
every.Add(match.Max{min})
}
}
if separator != "" {
every.Add(match.Contains{separator, true})
}
return every
}
func convertMatchers(matchers []match.Matcher) (match.Matcher, error) {
if m := glueMatchers(matchers); m != nil {
return m, nil
}
var (
val match.Primitive
idx int
)
for i, matcher := range matchers {
if p, ok := matcher.(match.Primitive); ok {
idx = i
val = p
if _, ok := matcher.(match.Raw); ok {
break
}
}
}
if val == nil {
return nil, fmt.Errorf("could not convert matchers %s: need at least one primitive", match.Matchers(matchers))
}
left := matchers[:idx]
var right []match.Matcher
if len(matchers) > idx+1 {
right = matchers[idx+1:]
}
tree := match.BTree{Value: val}
if len(left) > 0 {
l, err := convertMatchers(left)
if err != nil {
return nil, err
}
tree.Left = l
}
if len(right) > 0 {
r, err := convertMatchers(right)
if err != nil {
return nil, err
}
tree.Right = r
}
return tree, nil
}
func do(node node, s string) (m match.Matcher, err error) {
switch n := node.(type) {
case *nodeAnyOf, *nodePattern:
var matchers []match.Matcher
for _, desc := range node.children() {
m, err := do(desc, s)
if err != nil {
return nil, err
}
matchers = append(matchers, optimize(m))
}
if _, ok := node.(*nodeAnyOf); ok {
m = match.AnyOf{matchers}
} else {
m, err = convertMatchers(matchers)
if err != nil {
return nil, err
}
}
case *nodeList:
m = match.List{n.chars, n.not}
case *nodeRange:
m = match.Range{n.lo, n.hi, n.not}
case *nodeAny:
m = match.Any{s}
case *nodeSuper:
m = match.Super{}
case *nodeSingle:
m = match.Single{s}
case *nodeText:
m = match.Raw{n.text}
default:
return nil, fmt.Errorf("could not compile tree: unknown node type")
}
return optimize(m), nil
}
func compile(ast *nodePattern, s string) (Glob, error) {
g, err := do(ast, s)
if err != nil {
return nil, err
}
return g, nil
}