diff --git a/compiler/compiler.go b/compiler/compiler.go index 69ff118..3818d87 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -16,7 +16,6 @@ func Compile(tree *ast.Node, sep []rune) (match.Matcher, error) { if err != nil { return nil, err } - return m, nil } @@ -32,25 +31,29 @@ func compileNodes(ns []*ast.Node, sep []rune) ([]match.Matcher, error) { return matchers, nil } -func compile(tree *ast.Node, sep []rune) (m match.Matcher, err error) { +func compile(node *ast.Node, sep []rune) (m match.Matcher, err error) { if debug.Enabled { - debug.Enter() - debug.Logf("compiler: compiling %s", tree) + debug.EnterPrefix("compiler: compiling %s", node) defer func() { - debug.Logf("compiler: result %s", m) - debug.Leave() + if err != nil { + debug.Logf("->! %v", err) + } else { + debug.Logf("-> %s", m) + } + debug.LeavePrefix() }() } // todo this could be faster on pattern_alternatives_combine_lite (see glob_test.go) - if n := ast.Minimize(tree); n != nil { + if n := ast.Minimize(node); n != nil { + debug.Logf("minimized tree -> %s", node, n) r, err := compile(n, sep) if debug.Enabled { if err != nil { debug.Logf("compiler: compile minimized tree failed: %v", err) } else { debug.Logf("compiler: minimized tree") - debug.Logf("compiler: \t%s", tree) + debug.Logf("compiler: \t%s", node) debug.Logf("compiler: \t%s", n) } } @@ -59,19 +62,19 @@ func compile(tree *ast.Node, sep []rune) (m match.Matcher, err error) { } } - switch tree.Kind { + switch node.Kind { case ast.KindAnyOf: - matchers, err := compileNodes(tree.Children, sep) + matchers, err := compileNodes(node.Children, sep) if err != nil { return nil, err } return match.NewAnyOf(matchers...), nil case ast.KindPattern: - if len(tree.Children) == 0 { + if len(node.Children) == 0 { return match.NewNothing(), nil } - matchers, err := compileNodes(tree.Children, sep) + matchers, err := compileNodes(node.Children, sep) if err != nil { return nil, err } @@ -93,15 +96,15 @@ func compile(tree *ast.Node, sep []rune) (m match.Matcher, err error) { m = match.NewNothing() case ast.KindList: - l := tree.Value.(ast.List) + l := node.Value.(ast.List) m = match.NewList([]rune(l.Chars), l.Not) case ast.KindRange: - r := tree.Value.(ast.Range) + r := node.Value.(ast.Range) m = match.NewRange(r.Lo, r.Hi, r.Not) case ast.KindText: - t := tree.Value.(ast.Text) + t := node.Value.(ast.Text) m = match.NewText(t.Text) default: diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 13a765f..3f00e2e 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -11,44 +11,51 @@ import ( var separators = []rune{'.'} func TestCompiler(t *testing.T) { - for id, test := range []struct { - ast *ast.Node - result match.Matcher - sep []rune + for _, test := range []struct { + name string + ast *ast.Node + exp match.Matcher + sep []rune }{ { + // #0 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindText, ast.Text{"abc"}), ), - result: match.NewText("abc"), + exp: match.NewText("abc"), }, { + // #1 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindAny, nil), ), - sep: separators, - result: match.NewAny(separators), + sep: separators, + exp: match.NewAny(separators), }, { + // #2 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindAny, nil), ), - result: match.NewSuper(), + exp: match.NewSuper(), }, { + // #3 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindSuper, nil), ), - result: match.NewSuper(), + exp: match.NewSuper(), }, { + // #4 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindSingle, nil), ), - sep: separators, - result: match.NewSingle(separators), + sep: separators, + exp: match.NewSingle(separators), }, { + // #5 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindRange, ast.Range{ Lo: 'a', @@ -56,18 +63,20 @@ func TestCompiler(t *testing.T) { Not: true, }), ), - result: match.NewRange('a', 'z', true), + exp: match.NewRange('a', 'z', true), }, { + // #6 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindList, ast.List{ Chars: "abc", Not: true, }), ), - result: match.NewList([]rune{'a', 'b', 'c'}, true), + exp: match.NewList([]rune{'a', 'b', 'c'}, true), }, { + // #7 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindSingle, nil), @@ -75,28 +84,30 @@ func TestCompiler(t *testing.T) { ast.NewNode(ast.KindSingle, nil), ), sep: separators, - result: match.NewEveryOf([]match.Matcher{ + exp: match.NewEveryOf([]match.Matcher{ match.NewMin(3), match.NewAny(separators), }), }, { + // #8 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindSingle, nil), ast.NewNode(ast.KindSingle, nil), ast.NewNode(ast.KindSingle, nil), ), - result: match.NewMin(3), + exp: match.NewMin(3), }, { + // #9 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindText, ast.Text{"abc"}), ast.NewNode(ast.KindSingle, nil), ), sep: separators, - result: match.NewTree( + exp: match.NewTree( match.NewRow([]match.MatchIndexSizer{ match.NewText("abc"), match.NewSingle(separators), @@ -106,6 +117,7 @@ func TestCompiler(t *testing.T) { ), }, { + // #10 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindText, ast.Text{"/"}), ast.NewNode(ast.KindAnyOf, nil, @@ -115,7 +127,7 @@ func TestCompiler(t *testing.T) { ast.NewNode(ast.KindSuper, nil), ), sep: separators, - result: match.NewTree( + exp: match.NewTree( match.NewText("/"), nil, match.NewTree( @@ -129,6 +141,7 @@ func TestCompiler(t *testing.T) { ), }, { + // #11 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindSuper, nil), ast.NewNode(ast.KindSingle, nil), @@ -136,7 +149,7 @@ func TestCompiler(t *testing.T) { ast.NewNode(ast.KindSingle, nil), ), sep: separators, - result: match.NewTree( + exp: match.NewTree( match.NewRow([]match.MatchIndexSizer{ match.NewSingle(separators), match.NewText("abc"), @@ -147,28 +160,32 @@ func TestCompiler(t *testing.T) { ), }, { + // #12 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindText, ast.Text{"abc"}), ), - result: match.NewSuffix("abc"), + exp: match.NewSuffix("abc"), }, { + // #13 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindText, ast.Text{"abc"}), ast.NewNode(ast.KindAny, nil), ), - result: match.NewPrefix("abc"), + exp: match.NewPrefix("abc"), }, { + // #14 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindText, ast.Text{"abc"}), ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindText, ast.Text{"def"}), ), - result: match.NewPrefixSuffix("abc", "def"), + exp: match.NewPrefixSuffix("abc", "def"), }, { + // #15 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindAny, nil), @@ -177,9 +194,10 @@ func TestCompiler(t *testing.T) { ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindAny, nil), ), - result: match.NewContains("abc"), + exp: match.NewContains("abc"), }, { + // #16 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindAny, nil), @@ -189,14 +207,15 @@ func TestCompiler(t *testing.T) { ast.NewNode(ast.KindAny, nil), ), sep: separators, - result: match.NewTree( + exp: match.NewTree( match.NewText("abc"), match.NewAny(separators), match.NewAny(separators), ), }, { - // TODO: THIS! + // #17 + // pattern: "**?abc**?" ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindSuper, nil), ast.NewNode(ast.KindSingle, nil), @@ -204,19 +223,21 @@ func TestCompiler(t *testing.T) { ast.NewNode(ast.KindSuper, nil), ast.NewNode(ast.KindSingle, nil), ), - result: match.NewTree( + exp: match.NewTree( match.NewText("abc"), match.NewMin(1), match.NewMin(1), ), }, { + // #18 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindText, ast.Text{"abc"}), ), - result: match.NewText("abc"), + exp: match.NewText("abc"), }, { + // #19 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindAnyOf, nil, ast.NewNode(ast.KindPattern, nil, @@ -228,9 +249,10 @@ func TestCompiler(t *testing.T) { ), ), ), - result: match.NewText("abc"), + exp: match.NewText("abc"), }, { + // #20 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindAnyOf, nil, ast.NewNode(ast.KindPattern, nil, @@ -249,7 +271,7 @@ func TestCompiler(t *testing.T) { ), ), ), - result: match.NewTree( + exp: match.NewTree( match.NewText("abc"), nil, match.NewAnyOf( @@ -260,12 +282,13 @@ func TestCompiler(t *testing.T) { ), }, { + // #21 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindRange, ast.Range{Lo: 'a', Hi: 'z'}), ast.NewNode(ast.KindRange, ast.Range{Lo: 'a', Hi: 'x', Not: true}), ast.NewNode(ast.KindAny, nil), ), - result: match.NewTree( + exp: match.NewTree( match.NewRow([]match.MatchIndexSizer{ match.NewRange('a', 'z', false), match.NewRange('a', 'x', true), @@ -275,6 +298,7 @@ func TestCompiler(t *testing.T) { ), }, { + // #22 ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindAnyOf, nil, ast.NewNode(ast.KindPattern, nil, @@ -289,7 +313,7 @@ func TestCompiler(t *testing.T) { ), ), ), - result: match.NewRow([]match.MatchIndexSizer{ + exp: match.NewRow([]match.MatchIndexSizer{ match.NewText("abc"), match.MustIndexedSizedAnyOf( match.NewList([]rune{'a', 'b', 'c'}, false), @@ -299,15 +323,19 @@ func TestCompiler(t *testing.T) { }), }, } { - m, err := Compile(test.ast, test.sep) - if err != nil { - t.Errorf("compilation error: %s", err) - continue - } - - if !reflect.DeepEqual(m, test.result) { - t.Errorf("[%d] Compile():\nexp: %#v\nact: %#v\n\ngraphviz:\nexp:\n%s\nact:\n%s\n", id, test.result, m, match.Graphviz("", test.result.(match.Matcher)), match.Graphviz("", m.(match.Matcher))) - continue - } + t.Run(test.name, func(t *testing.T) { + act, err := Compile(test.ast, test.sep) + if err != nil { + t.Fatalf("compilation error: %s", err) + } + if !reflect.DeepEqual(act, test.exp) { + t.Errorf( + "Compile():\nact: %#v\nexp: %#v\n\ngraphviz:\n%s\n%s\n", + act, test.exp, + match.Graphviz("act", act.(match.Matcher)), + match.Graphviz("exp", test.exp.(match.Matcher)), + ) + } + }) } } diff --git a/internal/debug/debug_disabled.go b/internal/debug/debug_disabled.go index be1fe10..919be3a 100644 --- a/internal/debug/debug_disabled.go +++ b/internal/debug/debug_disabled.go @@ -4,6 +4,14 @@ package debug const Enabled = false -func Logf(_ string, _ ...interface{}) {} -func Enter() {} -func Leave() {} +func Logf(string, ...interface{}) {} +func Enter() {} +func Leave() {} +func EnterPrefix(string, ...interface{}) {} +func LeavePrefix() {} +func Indexing(n, s string) func(int, []int) { + panic("must never be called") +} +func Matching(n, s string) func(bool) { + panic("must never be called") +} diff --git a/internal/debug/debug_enabled.go b/internal/debug/debug_enabled.go index d6eaa48..4d7dee4 100644 --- a/internal/debug/debug_enabled.go +++ b/internal/debug/debug_enabled.go @@ -6,27 +6,59 @@ import ( "fmt" "os" "strings" - "sync/atomic" ) const Enabled = true -var i = new(int32) +var ( + i = 0 + prefix = map[int]string{} +) func Logf(f string, args ...interface{}) { - n := int(atomic.LoadInt32(i)) + if f != "" && prefix[i] != "" { + f = ": " + f + } fmt.Fprint(os.Stderr, - strings.Repeat(" ", n), - fmt.Sprintf("(%d) ", n), + strings.Repeat(" ", i), + fmt.Sprintf("(%d) ", i), + prefix[i], fmt.Sprintf(f, args...), "\n", ) } +func Indexing(name, s string) func(int, []int) { + EnterPrefix("%s: index: %q", name, s) + return func(index int, segments []int) { + Logf("-> %d, %v", index, segments) + LeavePrefix() + } +} + +func Matching(name, s string) func(bool) { + EnterPrefix("%s: match %q", name, s) + return func(ok bool) { + Logf("-> %t", ok) + LeavePrefix() + } +} + +func EnterPrefix(s string, args ...interface{}) { + Enter() + prefix[i] = fmt.Sprintf(s, args...) + Logf("") +} + +func LeavePrefix() { + prefix[i] = "" + Leave() +} + func Enter() { - atomic.AddInt32(i, 1) + i++ } func Leave() { - atomic.AddInt32(i, -1) + i-- } diff --git a/match/any_of.go b/match/any_of.go index 2010584..ac2cae9 100644 --- a/match/any_of.go +++ b/match/any_of.go @@ -2,6 +2,8 @@ package match import ( "fmt" + + "github.com/gobwas/glob/internal/debug" ) type AnyOf struct { @@ -41,7 +43,11 @@ func MustIndexedSizedAnyOf(ms ...Matcher) MatchIndexSizer { return NewAnyOf(ms...).(MatchIndexSizer) } -func (a AnyOf) Match(s string) bool { +func (a AnyOf) Match(s string) (ok bool) { + if debug.Enabled { + done := debug.Matching("any_of", s) + defer func() { done(ok) }() + } for _, m := range a.ms { if m.Match(s) { return true @@ -54,8 +60,10 @@ func (a AnyOf) MinLen() (n int) { return a.min } -func (a AnyOf) Content() []Matcher { - return a.ms +func (a AnyOf) Content(cb func(Matcher)) { + for _, m := range a.ms { + cb(m) + } } func (a AnyOf) String() string { @@ -67,10 +75,17 @@ type IndexedAnyOf struct { ms []MatchIndexer } -func (a IndexedAnyOf) Index(s string) (int, []int) { - index := -1 - segments := acquireSegments(len(s)) +func (a IndexedAnyOf) Index(s string) (index int, segments []int) { + if debug.Enabled { + done := debug.Indexing("any_of", s) + defer func() { done(index, segments) }() + } + index = -1 + segments = acquireSegments(len(s)) for _, m := range a.ms { + if debug.Enabled { + debug.Logf("indexing: any_of: trying %s", m) + } i, seg := m.Index(s) if i == -1 { continue diff --git a/match/any_of_test.go b/match/any_of_test.go index c989ff2..cf2c1ed 100644 --- a/match/any_of_test.go +++ b/match/any_of_test.go @@ -41,13 +41,15 @@ func TestIndexedAnyOf(t *testing.T) { []int{1}, }, } { - a := NewAnyOf(test.matchers...).(IndexedAnyOf) - index, segments := a.Index(test.fixture) - if index != test.index { - t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) - } - if !reflect.DeepEqual(segments, test.segments) { - t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) - } + t.Run("", func(t *testing.T) { + a := NewAnyOf(test.matchers...).(Indexer) + index, segments := a.Index(test.fixture) + if index != test.index { + t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) + } + if !reflect.DeepEqual(segments, test.segments) { + t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments) + } + }) } } diff --git a/match/debug.go b/match/debug.go index 2dd6a96..e936cc5 100644 --- a/match/debug.go +++ b/match/debug.go @@ -54,20 +54,12 @@ func graphviz(m Matcher, id string) string { } case Container: - fmt.Fprintf(buf, `"%s"[label="*AnyOf"];`, id) - for _, m := range v.Content() { + fmt.Fprintf(buf, `"%s"[label="Container(%T)"];`, id, m) + v.Content(func(m Matcher) { rnd := rand.Int63() fmt.Fprintf(buf, graphviz(m, fmt.Sprintf("%x", rnd))) fmt.Fprintf(buf, `"%s"->"%x";`, id, rnd) - } - - case EveryOf: - fmt.Fprintf(buf, `"%s"[label="EveryOf"];`, id) - for _, m := range v.ms { - rnd := rand.Int63() - fmt.Fprintf(buf, graphviz(m, fmt.Sprintf("%x", rnd))) - fmt.Fprintf(buf, `"%s"->"%x";`, id, rnd) - } + }) default: fmt.Fprintf(buf, `"%s"[label="%s"];`, id, m) diff --git a/match/every_of.go b/match/every_of.go index 89e453f..ed0c0a6 100644 --- a/match/every_of.go +++ b/match/every_of.go @@ -10,7 +10,10 @@ type EveryOf struct { } func NewEveryOf(ms []Matcher) Matcher { - e := EveryOf{ms, minLen(ms)} + e := EveryOf{ + ms: ms, + min: maxLen(ms), + } if mis, ok := MatchIndexers(ms); ok { return IndexedEveryOf{e, mis} } @@ -30,6 +33,12 @@ func (e EveryOf) Match(s string) bool { return true } +func (e EveryOf) Content(cb func(Matcher)) { + for _, m := range e.ms { + cb(m) + } +} + func (e EveryOf) String() string { return fmt.Sprintf("", e.ms) } diff --git a/match/match.go b/match/match.go index 71536b2..2592e43 100644 --- a/match/match.go +++ b/match/match.go @@ -37,7 +37,7 @@ type MatchIndexSizer interface { } type Container interface { - Content() []Matcher + Content(func(Matcher)) } func MatchIndexers(ms []Matcher) ([]MatchIndexer, bool) { diff --git a/match/optimize.go b/match/optimize.go index a07298f..15f7be2 100644 --- a/match/optimize.go +++ b/match/optimize.go @@ -3,23 +3,27 @@ package match import ( "fmt" + "github.com/gobwas/glob/internal/debug" "github.com/gobwas/glob/util/runes" ) -func Optimize(m Matcher) Matcher { +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 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)) @@ -73,12 +77,33 @@ func Optimize(m Matcher) Matcher { 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) (Matcher, error) { +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") } @@ -90,33 +115,40 @@ func Compile(ms []Matcher) (Matcher, error) { } var ( - idx = -1 - maxLen = -2 - indexer MatchIndexer + x = -1 + max = -2 + + wantText bool + indexer MatchIndexer ) for i, m := range ms { - mi, ok := m.(MatchIndexer) + mx, ok := m.(MatchIndexer) if !ok { continue } - if n := m.MinLen(); n > maxLen { - maxLen = n - idx = i - indexer = mi + _, 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[:idx] + left := ms[:x] var right []Matcher - if len(ms) > idx+1 { - right = ms[idx+1:] + if len(ms) > x+1 { + right = ms[x+1:] } var l, r Matcher - var err error if len(left) > 0 { l, err = Compile(left) if err != nil { @@ -239,40 +271,139 @@ func glueMatchersAsEvery(ms []Matcher) Matcher { 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 - } - } +type result struct { + ms []Matcher + matchers int + minLen int + nesting int +} + +func compareResult(a, b result) int { + if x := len(a.ms) - len(b.ms); x != 0 { + return x + } + if x := a.matchers - b.matchers; x != 0 { + return x + } + if x := b.minLen - a.minLen; x != 0 { + return x + } + if x := a.nesting - b.nesting; 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 } } - if result == nil { + return max +} + +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), + minLen: maxMinLen(cp), + nesting: maxNestingDepth(cp), + } + if debug.Enabled { + debug.EnterPrefix( + "intermediate: %s (matchers:%d, minlen:%d, nesting:%d)", + cp, r.matchers, r.minLen, 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 } - 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) + return best.ms } diff --git a/match/optimize_test.go b/match/optimize_test.go index 80408eb..c8258b0 100644 --- a/match/optimize_test.go +++ b/match/optimize_test.go @@ -3,12 +3,12 @@ package match import ( "reflect" "testing" - - "github.com/gobwas/glob/match" ) +var separators = []rune{'.'} + func TestCompile(t *testing.T) { - for id, test := range []struct { + for _, test := range []struct { in []Matcher exp Matcher }{ @@ -26,7 +26,7 @@ func TestCompile(t *testing.T) { }, NewEveryOf([]Matcher{ NewMin(1), - NewContains(string(separators)), + NewAny(separators), }), }, { @@ -47,7 +47,7 @@ func TestCompile(t *testing.T) { }, NewEveryOf([]Matcher{ NewMin(1), - NewContains("a"), + NewAny([]rune{'a'}), }), }, { @@ -58,7 +58,7 @@ func TestCompile(t *testing.T) { }, NewTree( NewText("c"), - NewBTree( + NewTree( NewSingle(separators), NewSuper(), nil, @@ -93,71 +93,73 @@ func TestCompile(t *testing.T) { }), }, } { - act, err := Compile(test.in) - if err != nil { - t.Errorf("#%d compile matchers error: %s", id, err) - continue - } - if !reflect.DeepEqual(act, test.exp) { - t.Errorf("#%d unexpected compile matchers result:\nact: %#v;\nexp: %#v", id, act, test.exp) - continue - } + t.Run("", func(t *testing.T) { + act, err := Compile(test.in) + if err != nil { + t.Fatalf("Compile() error: %s", err) + } + if !reflect.DeepEqual(act, test.exp) { + t.Errorf( + "Compile():\nact: %#v;\nexp: %#v;\ngraphviz:\n%s\n%s", + act, test.exp, + Graphviz("act", act), Graphviz("exp", test.exp), + ) + } + }) } } func TestMinimize(t *testing.T) { - for id, test := range []struct { - in, exp []match.Matcher + for _, test := range []struct { + in, exp []Matcher }{ { - []match.Matcher{ - match.NewRange('a', 'c', true), - match.NewList([]rune{'z', 't', 'e'}, false), - match.NewText("c"), - match.NewSingle(nil), - match.NewAny(nil), + in: []Matcher{ + NewRange('a', 'c', true), + NewList([]rune{'z', 't', 'e'}, false), + NewText("c"), + NewSingle(nil), + NewAny(nil), }, - []match.Matcher{ - match.NewRow( - 4, - []match.Matcher{ - match.NewRange('a', 'c', true), - match.NewList([]rune{'z', 't', 'e'}, false), - match.NewText("c"), - match.NewSingle(nil), - }..., - ), - match.NewAny(nil), + exp: []Matcher{ + NewRow([]MatchIndexSizer{ + NewRange('a', 'c', true), + NewList([]rune{'z', 't', 'e'}, false), + NewText("c"), + }), + NewMin(1), }, }, { - []match.Matcher{ - match.NewRange('a', 'c', true), - match.NewList([]rune{'z', 't', 'e'}, false), - match.NewText("c"), - match.NewSingle(nil), - match.NewAny(nil), - match.NewSingle(nil), - match.NewSingle(nil), - match.NewAny(nil), + in: []Matcher{ + NewRange('a', 'c', true), + NewList([]rune{'z', 't', 'e'}, false), + NewText("c"), + NewSingle(nil), + NewAny(nil), + NewSingle(nil), + NewSingle(nil), + NewAny(nil), }, - []match.Matcher{ - match.NewRow( - 3, - match.Matchers{ - match.NewRange('a', 'c', true), - match.NewList([]rune{'z', 't', 'e'}, false), - match.NewText("c"), - }..., - ), - match.NewMin(3), + exp: []Matcher{ + NewRow([]MatchIndexSizer{ + NewRange('a', 'c', true), + NewList([]rune{'z', 't', 'e'}, false), + NewText("c"), + }), + NewMin(3), }, }, } { - act := minimizeMatchers(test.in) - if !reflect.DeepEqual(act, test.exp) { - t.Errorf("#%d unexpected convert matchers 2 result:\nact: %#v\nexp: %#v", id, act, test.exp) - continue - } + t.Run("", func(t *testing.T) { + act := Minimize(test.in) + + if !reflect.DeepEqual(act, test.exp) { + t.Errorf( + "Minimize():\nact: %#v;\nexp: %#v", + act, test.exp, + ) + } + }) } } diff --git a/match/range.go b/match/range.go index e3120ac..30f4c52 100644 --- a/match/range.go +++ b/match/range.go @@ -3,6 +3,8 @@ package match import ( "fmt" "unicode/utf8" + + "github.com/gobwas/glob/internal/debug" ) type Range struct { @@ -22,7 +24,11 @@ func (self Range) RunesCount() int { return 1 } -func (self Range) Match(s string) bool { +func (self Range) Match(s string) (ok bool) { + if debug.Enabled { + done := debug.Matching("range", s) + defer func() { done(ok) }() + } r, w := utf8.DecodeRuneInString(s) if len(s) > w { return false @@ -33,7 +39,11 @@ func (self Range) Match(s string) bool { return inRange == !self.Not } -func (self Range) Index(s string) (int, []int) { +func (self Range) Index(s string) (index int, segments []int) { + if debug.Enabled { + done := debug.Indexing("range", s) + defer func() { done(index, segments) }() + } for i, r := range s { if self.Not != (r >= self.Lo && r <= self.Hi) { return i, segmentsByRuneLength[utf8.RuneLen(r)] diff --git a/match/row.go b/match/row.go index 80d566e..88340fb 100644 --- a/match/row.go +++ b/match/row.go @@ -4,6 +4,7 @@ import ( "fmt" "unicode/utf8" + "github.com/gobwas/glob/internal/debug" "github.com/gobwas/glob/util/runes" ) @@ -25,7 +26,11 @@ func NewRow(ms []MatchIndexSizer) Row { } } -func (r Row) Match(s string) bool { +func (r Row) Match(s string) (ok bool) { + if debug.Enabled { + done := debug.Matching("row", s) + defer func() { done(ok) }() + } if !runes.ExactlyRunesCount(s, r.runes) { return false } @@ -40,8 +45,14 @@ func (r Row) RunesCount() int { return r.runes } -func (r Row) Index(s string) (int, []int) { - for j := 0; j < len(s)-r.runes; { +func (r Row) Index(s string) (index int, segments []int) { + if debug.Enabled { + done := debug.Indexing("row", s) + debug.Logf("row: %d vs %d", len(s), r.runes) + defer func() { done(index, segments) }() + } + + for j := 0; j <= len(s)-r.runes; { // NOTE: using len() here to avoid counting runes. i, _ := r.ms[0].Index(s[j:]) if i == -1 { return -1, nil @@ -55,8 +66,14 @@ func (r Row) Index(s string) (int, []int) { return -1, nil } +func (r Row) Content(cb func(Matcher)) { + for _, m := range r.ms { + cb(m) + } +} + func (r Row) String() string { - return fmt.Sprintf("", r.runes, r.ms) + return fmt.Sprintf("", r.runes, r.ms) } func (r Row) matchAll(s string) bool { diff --git a/match/single.go b/match/single.go index 60d810e..25e2a07 100644 --- a/match/single.go +++ b/match/single.go @@ -42,5 +42,8 @@ func (s Single) Index(v string) (int, []int) { } func (s Single) String() string { + if len(s.sep) == 0 { + return "" + } return fmt.Sprintf("", string(s.sep)) } diff --git a/match/tree.go b/match/tree.go index efe5bba..8173cc1 100644 --- a/match/tree.go +++ b/match/tree.go @@ -74,29 +74,32 @@ func (t Tree) MinLen() int { return t.minLen } +func (t Tree) Content(cb func(Matcher)) { + if t.left != nil { + cb(t.left) + } + cb(t.value) + if t.right != nil { + cb(t.right) + } +} + func (t Tree) Match(s string) (ok bool) { if debug.Enabled { - debug.Enter() - debug.Logf("tree: matching %q: %v", s, t) - defer func(s string) { - debug.Logf("tree: result: %q -> %v", s, ok) - debug.Leave() - }(s) + done := debug.Matching("tree", s) + defer func() { done(ok) }() } offset, limit := t.offsetLimit(s) q := s[offset : len(s)-limit] if debug.Enabled { - debug.Logf("tree: offset/limit: %d/%d %q of %q", offset, limit, q, s) + debug.Logf("offset/limit: %d/%d: %q of %q", offset, limit, q, s) } for len(q) >= t.vrunes { // search for matching part in substring index, segments := t.value.Index(q) - if debug.Enabled { - debug.Logf("tree: index #%d %q (%v)", index, q, t.value) - } if index == -1 { releaseSegments(segments) return false @@ -110,7 +113,7 @@ func (t Tree) Match(s string) (ok bool) { left = l == "" } if debug.Enabled { - debug.Logf("tree: left %q %v", l, left) + debug.Logf("left %q: -> %t", l, left) } if left { for _, seg := range segments { @@ -124,7 +127,7 @@ func (t Tree) Match(s string) (ok bool) { right = r == "" } if debug.Enabled { - debug.Logf("tree: right %q %v", r, right) + debug.Logf("right %q: -> %t", r, right) } if right { releaseSegments(segments) diff --git a/match/util.go b/match/util.go index 6dc60a1..f67f902 100644 --- a/match/util.go +++ b/match/util.go @@ -9,3 +9,13 @@ func minLen(ms []Matcher) (min int) { } return min } + +func maxLen(ms []Matcher) (max int) { + for i, m := range ms { + n := m.MinLen() + if i == 0 || n > max { + max = n + } + } + return max +}