This commit is contained in:
gobwas 2019-02-06 23:43:38 +03:00
parent abc7140723
commit 4a52abd846
16 changed files with 486 additions and 221 deletions

View File

@ -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:

View File

@ -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)),
)
}
})
}
}

View File

@ -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")
}

View File

@ -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--
}

View File

@ -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

View File

@ -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)
}
})
}
}

View File

@ -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)

View File

@ -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("<every_of:[%s]>", e.ms)
}

View File

@ -37,7 +37,7 @@ type MatchIndexSizer interface {
}
type Container interface {
Content() []Matcher
Content(func(Matcher))
}
func MatchIndexers(ms []Matcher) ([]MatchIndexer, bool) {

View File

@ -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
}

View File

@ -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,
)
}
})
}
}

View File

@ -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)]

View File

@ -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("<row_%d:[%s]>", r.runes, r.ms)
return fmt.Sprintf("<row_%d:%s>", r.runes, r.ms)
}
func (r Row) matchAll(s string) bool {

View File

@ -42,5 +42,8 @@ func (s Single) Index(v string) (int, []int) {
}
func (s Single) String() string {
if len(s.sep) == 0 {
return "<single>"
}
return fmt.Sprintf("<single:![%s]>", string(s.sep))
}

View File

@ -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)

View File

@ -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
}