123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525 |
- package compiler
- // TODO use constructor with all matchers, and to their structs private
- // TODO glue multiple Text nodes (like after QuoteMeta)
- import (
- "fmt"
- "reflect"
- "github.com/gobwas/glob/match"
- "github.com/gobwas/glob/syntax/ast"
- "github.com/gobwas/glob/util/runes"
- )
- func optimizeMatcher(matcher match.Matcher) match.Matcher {
- switch m := matcher.(type) {
- case match.Any:
- if len(m.Separators) == 0 {
- return match.NewSuper()
- }
- case match.AnyOf:
- if len(m.Matchers) == 1 {
- return m.Matchers[0]
- }
- return m
- case match.List:
- if m.Not == false && len(m.List) == 1 {
- return match.NewText(string(m.List))
- }
- return m
- case match.BTree:
- m.Left = optimizeMatcher(m.Left)
- m.Right = optimizeMatcher(m.Right)
- r, ok := m.Value.(match.Text)
- if !ok {
- return m
- }
- var (
- leftNil = m.Left == nil
- rightNil = m.Right == nil
- )
- if leftNil && rightNil {
- return match.NewText(r.Str)
- }
- _, leftSuper := m.Left.(match.Super)
- lp, leftPrefix := m.Left.(match.Prefix)
- la, leftAny := m.Left.(match.Any)
- _, rightSuper := m.Right.(match.Super)
- rs, rightSuffix := m.Right.(match.Suffix)
- ra, rightAny := m.Right.(match.Any)
- switch {
- case leftSuper && rightSuper:
- return match.NewContains(r.Str, false)
- case leftSuper && rightNil:
- return match.NewSuffix(r.Str)
- case rightSuper && leftNil:
- return match.NewPrefix(r.Str)
- case leftNil && rightSuffix:
- return match.NewPrefixSuffix(r.Str, rs.Suffix)
- case rightNil && leftPrefix:
- return match.NewPrefixSuffix(lp.Prefix, r.Str)
- case rightNil && leftAny:
- return match.NewSuffixAny(r.Str, la.Separators)
- case leftNil && rightAny:
- return match.NewPrefixAny(r.Str, ra.Separators)
- }
- return m
- }
- return matcher
- }
- func compileMatchers(matchers []match.Matcher) (match.Matcher, error) {
- if len(matchers) == 0 {
- return nil, fmt.Errorf("compile error: need at least one matcher")
- }
- if len(matchers) == 1 {
- return matchers[0], nil
- }
- if m := glueMatchers(matchers); m != nil {
- return m, nil
- }
- idx := -1
- maxLen := -1
- var val match.Matcher
- for i, matcher := range matchers {
- if l := matcher.Len(); l != -1 && l >= maxLen {
- maxLen = l
- idx = i
- val = matcher
- }
- }
- if val == nil { // not found matcher with static length
- r, err := compileMatchers(matchers[1:])
- if err != nil {
- return nil, err
- }
- return match.NewBTree(matchers[0], nil, r), nil
- }
- left := matchers[:idx]
- var right []match.Matcher
- if len(matchers) > idx+1 {
- right = matchers[idx+1:]
- }
- var l, r match.Matcher
- var err error
- if len(left) > 0 {
- l, err = compileMatchers(left)
- if err != nil {
- return nil, err
- }
- }
- if len(right) > 0 {
- r, err = compileMatchers(right)
- if err != nil {
- return nil, err
- }
- }
- return match.NewBTree(val, l, r), nil
- }
- func glueMatchers(matchers []match.Matcher) match.Matcher {
- if m := glueMatchersAsEvery(matchers); m != nil {
- return m
- }
- if m := glueMatchersAsRow(matchers); m != nil {
- return m
- }
- return nil
- }
- func glueMatchersAsRow(matchers []match.Matcher) match.Matcher {
- if len(matchers) <= 1 {
- return nil
- }
- var (
- c []match.Matcher
- l int
- )
- for _, matcher := range matchers {
- if ml := matcher.Len(); ml == -1 {
- return nil
- } else {
- c = append(c, matcher)
- l += ml
- }
- }
- return match.NewRow(l, c...)
- }
- func glueMatchersAsEvery(matchers []match.Matcher) match.Matcher {
- if len(matchers) <= 1 {
- return nil
- }
- var (
- hasAny bool
- hasSuper bool
- hasSingle bool
- min int
- separator []rune
- )
- for i, matcher := range matchers {
- var sep []rune
- switch m := matcher.(type) {
- case match.Super:
- sep = []rune{}
- 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 runes.Equal(sep, separator) {
- continue
- }
- return nil
- }
- if hasSuper && !hasAny && !hasSingle {
- return match.NewSuper()
- }
- if hasAny && !hasSuper && !hasSingle {
- return match.NewAny(separator)
- }
- if (hasAny || hasSuper) && min > 0 && len(separator) == 0 {
- return match.NewMin(min)
- }
- every := match.NewEveryOf()
- if min > 0 {
- every.Add(match.NewMin(min))
- if !hasAny && !hasSuper {
- every.Add(match.NewMax(min))
- }
- }
- if len(separator) > 0 {
- every.Add(match.NewContains(string(separator), true))
- }
- return every
- }
- func minimizeMatchers(matchers []match.Matcher) []match.Matcher {
- var done match.Matcher
- var left, right, count int
- for l := 0; l < len(matchers); l++ {
- for r := len(matchers); r > l; r-- {
- if glued := glueMatchers(matchers[l:r]); glued != nil {
- var swap bool
- if done == nil {
- swap = true
- } else {
- cl, gl := done.Len(), glued.Len()
- swap = cl > -1 && gl > -1 && gl > cl
- swap = swap || count < r-l
- }
- if swap {
- done = glued
- left = l
- right = r
- count = r - l
- }
- }
- }
- }
- if done == nil {
- return matchers
- }
- next := append(append([]match.Matcher{}, matchers[:left]...), done)
- if right < len(matchers) {
- next = append(next, matchers[right:]...)
- }
- if len(next) == len(matchers) {
- return next
- }
- return minimizeMatchers(next)
- }
- // minimizeAnyOf tries to apply some heuristics to minimize number of nodes in given tree
- func minimizeTree(tree *ast.Node) *ast.Node {
- switch tree.Kind {
- case ast.KindAnyOf:
- return minimizeTreeAnyOf(tree)
- default:
- return nil
- }
- }
- // minimizeAnyOf tries to find common children of given node of AnyOf pattern
- // it searches for common children from left and from right
- // if any common children are found – then it returns new optimized ast tree
- // else it returns nil
- func minimizeTreeAnyOf(tree *ast.Node) *ast.Node {
- if !areOfSameKind(tree.Children, ast.KindPattern) {
- return nil
- }
- commonLeft, commonRight := commonChildren(tree.Children)
- commonLeftCount, commonRightCount := len(commonLeft), len(commonRight)
- if commonLeftCount == 0 && commonRightCount == 0 { // there are no common parts
- return nil
- }
- var result []*ast.Node
- if commonLeftCount > 0 {
- result = append(result, ast.NewNode(ast.KindPattern, nil, commonLeft...))
- }
- var anyOf []*ast.Node
- for _, child := range tree.Children {
- reuse := child.Children[commonLeftCount : len(child.Children)-commonRightCount]
- var node *ast.Node
- if len(reuse) == 0 {
- // this pattern is completely reduced by commonLeft and commonRight patterns
- // so it become nothing
- node = ast.NewNode(ast.KindNothing, nil)
- } else {
- node = ast.NewNode(ast.KindPattern, nil, reuse...)
- }
- anyOf = appendIfUnique(anyOf, node)
- }
- switch {
- case len(anyOf) == 1 && anyOf[0].Kind != ast.KindNothing:
- result = append(result, anyOf[0])
- case len(anyOf) > 1:
- result = append(result, ast.NewNode(ast.KindAnyOf, nil, anyOf...))
- }
- if commonRightCount > 0 {
- result = append(result, ast.NewNode(ast.KindPattern, nil, commonRight...))
- }
- return ast.NewNode(ast.KindPattern, nil, result...)
- }
- func commonChildren(nodes []*ast.Node) (commonLeft, commonRight []*ast.Node) {
- if len(nodes) <= 1 {
- return
- }
- // find node that has least number of children
- idx := leastChildren(nodes)
- if idx == -1 {
- return
- }
- tree := nodes[idx]
- treeLength := len(tree.Children)
- // allocate max able size for rightCommon slice
- // to get ability insert elements in reverse order (from end to start)
- // without sorting
- commonRight = make([]*ast.Node, treeLength)
- lastRight := treeLength // will use this to get results as commonRight[lastRight:]
- var (
- breakLeft bool
- breakRight bool
- commonTotal int
- )
- for i, j := 0, treeLength-1; commonTotal < treeLength && j >= 0 && !(breakLeft && breakRight); i, j = i+1, j-1 {
- treeLeft := tree.Children[i]
- treeRight := tree.Children[j]
- for k := 0; k < len(nodes) && !(breakLeft && breakRight); k++ {
- // skip least children node
- if k == idx {
- continue
- }
- restLeft := nodes[k].Children[i]
- restRight := nodes[k].Children[j+len(nodes[k].Children)-treeLength]
- breakLeft = breakLeft || !treeLeft.Equal(restLeft)
- // disable searching for right common parts, if left part is already overlapping
- breakRight = breakRight || (!breakLeft && j <= i)
- breakRight = breakRight || !treeRight.Equal(restRight)
- }
- if !breakLeft {
- commonTotal++
- commonLeft = append(commonLeft, treeLeft)
- }
- if !breakRight {
- commonTotal++
- lastRight = j
- commonRight[j] = treeRight
- }
- }
- commonRight = commonRight[lastRight:]
- return
- }
- func appendIfUnique(target []*ast.Node, val *ast.Node) []*ast.Node {
- for _, n := range target {
- if reflect.DeepEqual(n, val) {
- return target
- }
- }
- return append(target, val)
- }
- func areOfSameKind(nodes []*ast.Node, kind ast.Kind) bool {
- for _, n := range nodes {
- if n.Kind != kind {
- return false
- }
- }
- return true
- }
- func leastChildren(nodes []*ast.Node) int {
- min := -1
- idx := -1
- for i, n := range nodes {
- if idx == -1 || (len(n.Children) < min) {
- min = len(n.Children)
- idx = i
- }
- }
- return idx
- }
- func compileTreeChildren(tree *ast.Node, sep []rune) ([]match.Matcher, error) {
- var matchers []match.Matcher
- for _, desc := range tree.Children {
- m, err := compile(desc, sep)
- if err != nil {
- return nil, err
- }
- matchers = append(matchers, optimizeMatcher(m))
- }
- return matchers, nil
- }
- func compile(tree *ast.Node, sep []rune) (m match.Matcher, err error) {
- switch tree.Kind {
- case ast.KindAnyOf:
- // todo this could be faster on pattern_alternatives_combine_lite (see glob_test.go)
- if n := minimizeTree(tree); n != nil {
- return compile(n, sep)
- }
- matchers, err := compileTreeChildren(tree, sep)
- if err != nil {
- return nil, err
- }
- return match.NewAnyOf(matchers...), nil
- case ast.KindPattern:
- if len(tree.Children) == 0 {
- return match.NewNothing(), nil
- }
- matchers, err := compileTreeChildren(tree, sep)
- if err != nil {
- return nil, err
- }
- m, err = compileMatchers(minimizeMatchers(matchers))
- if err != nil {
- return nil, err
- }
- case ast.KindAny:
- m = match.NewAny(sep)
- case ast.KindSuper:
- m = match.NewSuper()
- case ast.KindSingle:
- m = match.NewSingle(sep)
- case ast.KindNothing:
- m = match.NewNothing()
- case ast.KindList:
- l := tree.Value.(ast.List)
- m = match.NewList([]rune(l.Chars), l.Not)
- case ast.KindRange:
- r := tree.Value.(ast.Range)
- m = match.NewRange(r.Lo, r.Hi, r.Not)
- case ast.KindText:
- t := tree.Value.(ast.Text)
- m = match.NewText(t.Text)
- default:
- return nil, fmt.Errorf("could not compile tree: unknown node type")
- }
- return optimizeMatcher(m), nil
- }
- func Compile(tree *ast.Node, sep []rune) (match.Matcher, error) {
- m, err := compile(tree, sep)
- if err != nil {
- return nil, err
- }
- return m, nil
- }
|