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 { if err != nil {
return nil, err return nil, err
} }
return m, nil return m, nil
} }
@ -32,25 +31,29 @@ func compileNodes(ns []*ast.Node, sep []rune) ([]match.Matcher, error) {
return matchers, nil 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 { if debug.Enabled {
debug.Enter() debug.EnterPrefix("compiler: compiling %s", node)
debug.Logf("compiler: compiling %s", tree)
defer func() { defer func() {
debug.Logf("compiler: result %s", m) if err != nil {
debug.Leave() 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) // 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) r, err := compile(n, sep)
if debug.Enabled { if debug.Enabled {
if err != nil { if err != nil {
debug.Logf("compiler: compile minimized tree failed: %v", err) debug.Logf("compiler: compile minimized tree failed: %v", err)
} else { } else {
debug.Logf("compiler: minimized tree") debug.Logf("compiler: minimized tree")
debug.Logf("compiler: \t%s", tree) debug.Logf("compiler: \t%s", node)
debug.Logf("compiler: \t%s", n) 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: case ast.KindAnyOf:
matchers, err := compileNodes(tree.Children, sep) matchers, err := compileNodes(node.Children, sep)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return match.NewAnyOf(matchers...), nil return match.NewAnyOf(matchers...), nil
case ast.KindPattern: case ast.KindPattern:
if len(tree.Children) == 0 { if len(node.Children) == 0 {
return match.NewNothing(), nil return match.NewNothing(), nil
} }
matchers, err := compileNodes(tree.Children, sep) matchers, err := compileNodes(node.Children, sep)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -93,15 +96,15 @@ func compile(tree *ast.Node, sep []rune) (m match.Matcher, err error) {
m = match.NewNothing() m = match.NewNothing()
case ast.KindList: case ast.KindList:
l := tree.Value.(ast.List) l := node.Value.(ast.List)
m = match.NewList([]rune(l.Chars), l.Not) m = match.NewList([]rune(l.Chars), l.Not)
case ast.KindRange: case ast.KindRange:
r := tree.Value.(ast.Range) r := node.Value.(ast.Range)
m = match.NewRange(r.Lo, r.Hi, r.Not) m = match.NewRange(r.Lo, r.Hi, r.Not)
case ast.KindText: case ast.KindText:
t := tree.Value.(ast.Text) t := node.Value.(ast.Text)
m = match.NewText(t.Text) m = match.NewText(t.Text)
default: default:

View File

@ -11,44 +11,51 @@ import (
var separators = []rune{'.'} var separators = []rune{'.'}
func TestCompiler(t *testing.T) { func TestCompiler(t *testing.T) {
for id, test := range []struct { for _, test := range []struct {
name string
ast *ast.Node ast *ast.Node
result match.Matcher exp match.Matcher
sep []rune sep []rune
}{ }{
{ {
// #0
ast: ast.NewNode(ast.KindPattern, nil, ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindText, ast.Text{"abc"}), ast.NewNode(ast.KindText, ast.Text{"abc"}),
), ),
result: match.NewText("abc"), exp: match.NewText("abc"),
}, },
{ {
// #1
ast: ast.NewNode(ast.KindPattern, nil, ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindAny, nil),
), ),
sep: separators, sep: separators,
result: match.NewAny(separators), exp: match.NewAny(separators),
}, },
{ {
// #2
ast: ast.NewNode(ast.KindPattern, nil, ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindAny, nil),
), ),
result: match.NewSuper(), exp: match.NewSuper(),
}, },
{ {
// #3
ast: ast.NewNode(ast.KindPattern, nil, ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindSuper, nil), ast.NewNode(ast.KindSuper, nil),
), ),
result: match.NewSuper(), exp: match.NewSuper(),
}, },
{ {
// #4
ast: ast.NewNode(ast.KindPattern, nil, ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindSingle, nil), ast.NewNode(ast.KindSingle, nil),
), ),
sep: separators, sep: separators,
result: match.NewSingle(separators), exp: match.NewSingle(separators),
}, },
{ {
// #5
ast: ast.NewNode(ast.KindPattern, nil, ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindRange, ast.Range{ ast.NewNode(ast.KindRange, ast.Range{
Lo: 'a', Lo: 'a',
@ -56,18 +63,20 @@ func TestCompiler(t *testing.T) {
Not: true, Not: true,
}), }),
), ),
result: match.NewRange('a', 'z', true), exp: match.NewRange('a', 'z', true),
}, },
{ {
// #6
ast: ast.NewNode(ast.KindPattern, nil, ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindList, ast.List{ ast.NewNode(ast.KindList, ast.List{
Chars: "abc", Chars: "abc",
Not: true, 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: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindAny, nil),
ast.NewNode(ast.KindSingle, nil), ast.NewNode(ast.KindSingle, nil),
@ -75,28 +84,30 @@ func TestCompiler(t *testing.T) {
ast.NewNode(ast.KindSingle, nil), ast.NewNode(ast.KindSingle, nil),
), ),
sep: separators, sep: separators,
result: match.NewEveryOf([]match.Matcher{ exp: match.NewEveryOf([]match.Matcher{
match.NewMin(3), match.NewMin(3),
match.NewAny(separators), match.NewAny(separators),
}), }),
}, },
{ {
// #8
ast: ast.NewNode(ast.KindPattern, nil, ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindAny, nil),
ast.NewNode(ast.KindSingle, nil), ast.NewNode(ast.KindSingle, nil),
ast.NewNode(ast.KindSingle, 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: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindAny, nil),
ast.NewNode(ast.KindText, ast.Text{"abc"}), ast.NewNode(ast.KindText, ast.Text{"abc"}),
ast.NewNode(ast.KindSingle, nil), ast.NewNode(ast.KindSingle, nil),
), ),
sep: separators, sep: separators,
result: match.NewTree( exp: match.NewTree(
match.NewRow([]match.MatchIndexSizer{ match.NewRow([]match.MatchIndexSizer{
match.NewText("abc"), match.NewText("abc"),
match.NewSingle(separators), match.NewSingle(separators),
@ -106,6 +117,7 @@ func TestCompiler(t *testing.T) {
), ),
}, },
{ {
// #10
ast: ast.NewNode(ast.KindPattern, nil, ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindText, ast.Text{"/"}), ast.NewNode(ast.KindText, ast.Text{"/"}),
ast.NewNode(ast.KindAnyOf, nil, ast.NewNode(ast.KindAnyOf, nil,
@ -115,7 +127,7 @@ func TestCompiler(t *testing.T) {
ast.NewNode(ast.KindSuper, nil), ast.NewNode(ast.KindSuper, nil),
), ),
sep: separators, sep: separators,
result: match.NewTree( exp: match.NewTree(
match.NewText("/"), match.NewText("/"),
nil, nil,
match.NewTree( match.NewTree(
@ -129,6 +141,7 @@ func TestCompiler(t *testing.T) {
), ),
}, },
{ {
// #11
ast: ast.NewNode(ast.KindPattern, nil, ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindSuper, nil), ast.NewNode(ast.KindSuper, nil),
ast.NewNode(ast.KindSingle, nil), ast.NewNode(ast.KindSingle, nil),
@ -136,7 +149,7 @@ func TestCompiler(t *testing.T) {
ast.NewNode(ast.KindSingle, nil), ast.NewNode(ast.KindSingle, nil),
), ),
sep: separators, sep: separators,
result: match.NewTree( exp: match.NewTree(
match.NewRow([]match.MatchIndexSizer{ match.NewRow([]match.MatchIndexSizer{
match.NewSingle(separators), match.NewSingle(separators),
match.NewText("abc"), match.NewText("abc"),
@ -147,28 +160,32 @@ func TestCompiler(t *testing.T) {
), ),
}, },
{ {
// #12
ast: ast.NewNode(ast.KindPattern, nil, ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindAny, nil),
ast.NewNode(ast.KindText, ast.Text{"abc"}), ast.NewNode(ast.KindText, ast.Text{"abc"}),
), ),
result: match.NewSuffix("abc"), exp: match.NewSuffix("abc"),
}, },
{ {
// #13
ast: ast.NewNode(ast.KindPattern, nil, ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindText, ast.Text{"abc"}), ast.NewNode(ast.KindText, ast.Text{"abc"}),
ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindAny, nil),
), ),
result: match.NewPrefix("abc"), exp: match.NewPrefix("abc"),
}, },
{ {
// #14
ast: ast.NewNode(ast.KindPattern, nil, ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindText, ast.Text{"abc"}), ast.NewNode(ast.KindText, ast.Text{"abc"}),
ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindAny, nil),
ast.NewNode(ast.KindText, ast.Text{"def"}), 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: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindAny, 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),
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: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindAny, 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), ast.NewNode(ast.KindAny, nil),
), ),
sep: separators, sep: separators,
result: match.NewTree( exp: match.NewTree(
match.NewText("abc"), match.NewText("abc"),
match.NewAny(separators), match.NewAny(separators),
match.NewAny(separators), match.NewAny(separators),
), ),
}, },
{ {
// TODO: THIS! // #17
// pattern: "**?abc**?"
ast: ast.NewNode(ast.KindPattern, nil, ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindSuper, nil), ast.NewNode(ast.KindSuper, nil),
ast.NewNode(ast.KindSingle, nil), ast.NewNode(ast.KindSingle, nil),
@ -204,19 +223,21 @@ func TestCompiler(t *testing.T) {
ast.NewNode(ast.KindSuper, nil), ast.NewNode(ast.KindSuper, nil),
ast.NewNode(ast.KindSingle, nil), ast.NewNode(ast.KindSingle, nil),
), ),
result: match.NewTree( exp: match.NewTree(
match.NewText("abc"), match.NewText("abc"),
match.NewMin(1), match.NewMin(1),
match.NewMin(1), match.NewMin(1),
), ),
}, },
{ {
// #18
ast: ast.NewNode(ast.KindPattern, nil, ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindText, ast.Text{"abc"}), ast.NewNode(ast.KindText, ast.Text{"abc"}),
), ),
result: match.NewText("abc"), exp: match.NewText("abc"),
}, },
{ {
// #19
ast: ast.NewNode(ast.KindPattern, nil, ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAnyOf, nil, ast.NewNode(ast.KindAnyOf, nil,
ast.NewNode(ast.KindPattern, 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: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAnyOf, nil, ast.NewNode(ast.KindAnyOf, nil,
ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindPattern, nil,
@ -249,7 +271,7 @@ func TestCompiler(t *testing.T) {
), ),
), ),
), ),
result: match.NewTree( exp: match.NewTree(
match.NewText("abc"), match.NewText("abc"),
nil, nil,
match.NewAnyOf( match.NewAnyOf(
@ -260,12 +282,13 @@ func TestCompiler(t *testing.T) {
), ),
}, },
{ {
// #21
ast: ast.NewNode(ast.KindPattern, nil, 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: 'z'}),
ast.NewNode(ast.KindRange, ast.Range{Lo: 'a', Hi: 'x', Not: true}), ast.NewNode(ast.KindRange, ast.Range{Lo: 'a', Hi: 'x', Not: true}),
ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindAny, nil),
), ),
result: match.NewTree( exp: match.NewTree(
match.NewRow([]match.MatchIndexSizer{ match.NewRow([]match.MatchIndexSizer{
match.NewRange('a', 'z', false), match.NewRange('a', 'z', false),
match.NewRange('a', 'x', true), match.NewRange('a', 'x', true),
@ -275,6 +298,7 @@ func TestCompiler(t *testing.T) {
), ),
}, },
{ {
// #22
ast: ast.NewNode(ast.KindPattern, nil, ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAnyOf, nil, ast.NewNode(ast.KindAnyOf, nil,
ast.NewNode(ast.KindPattern, 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.NewText("abc"),
match.MustIndexedSizedAnyOf( match.MustIndexedSizedAnyOf(
match.NewList([]rune{'a', 'b', 'c'}, false), match.NewList([]rune{'a', 'b', 'c'}, false),
@ -299,15 +323,19 @@ func TestCompiler(t *testing.T) {
}), }),
}, },
} { } {
m, err := Compile(test.ast, test.sep) t.Run(test.name, func(t *testing.T) {
act, err := Compile(test.ast, test.sep)
if err != nil { if err != nil {
t.Errorf("compilation error: %s", err) t.Fatalf("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
} }
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 const Enabled = false
func Logf(_ string, _ ...interface{}) {} func Logf(string, ...interface{}) {}
func Enter() {} func Enter() {}
func Leave() {} 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" "fmt"
"os" "os"
"strings" "strings"
"sync/atomic"
) )
const Enabled = true const Enabled = true
var i = new(int32) var (
i = 0
prefix = map[int]string{}
)
func Logf(f string, args ...interface{}) { func Logf(f string, args ...interface{}) {
n := int(atomic.LoadInt32(i)) if f != "" && prefix[i] != "" {
f = ": " + f
}
fmt.Fprint(os.Stderr, fmt.Fprint(os.Stderr,
strings.Repeat(" ", n), strings.Repeat(" ", i),
fmt.Sprintf("(%d) ", n), fmt.Sprintf("(%d) ", i),
prefix[i],
fmt.Sprintf(f, args...), fmt.Sprintf(f, args...),
"\n", "\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() { func Enter() {
atomic.AddInt32(i, 1) i++
} }
func Leave() { func Leave() {
atomic.AddInt32(i, -1) i--
} }

View File

@ -2,6 +2,8 @@ package match
import ( import (
"fmt" "fmt"
"github.com/gobwas/glob/internal/debug"
) )
type AnyOf struct { type AnyOf struct {
@ -41,7 +43,11 @@ func MustIndexedSizedAnyOf(ms ...Matcher) MatchIndexSizer {
return NewAnyOf(ms...).(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 { for _, m := range a.ms {
if m.Match(s) { if m.Match(s) {
return true return true
@ -54,8 +60,10 @@ func (a AnyOf) MinLen() (n int) {
return a.min return a.min
} }
func (a AnyOf) Content() []Matcher { func (a AnyOf) Content(cb func(Matcher)) {
return a.ms for _, m := range a.ms {
cb(m)
}
} }
func (a AnyOf) String() string { func (a AnyOf) String() string {
@ -67,10 +75,17 @@ type IndexedAnyOf struct {
ms []MatchIndexer ms []MatchIndexer
} }
func (a IndexedAnyOf) Index(s string) (int, []int) { func (a IndexedAnyOf) Index(s string) (index int, segments []int) {
index := -1 if debug.Enabled {
segments := acquireSegments(len(s)) done := debug.Indexing("any_of", s)
defer func() { done(index, segments) }()
}
index = -1
segments = acquireSegments(len(s))
for _, m := range a.ms { for _, m := range a.ms {
if debug.Enabled {
debug.Logf("indexing: any_of: trying %s", m)
}
i, seg := m.Index(s) i, seg := m.Index(s)
if i == -1 { if i == -1 {
continue continue

View File

@ -41,7 +41,8 @@ func TestIndexedAnyOf(t *testing.T) {
[]int{1}, []int{1},
}, },
} { } {
a := NewAnyOf(test.matchers...).(IndexedAnyOf) t.Run("", func(t *testing.T) {
a := NewAnyOf(test.matchers...).(Indexer)
index, segments := a.Index(test.fixture) index, segments := a.Index(test.fixture)
if index != test.index { if index != test.index {
t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index) t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index)
@ -49,5 +50,6 @@ func TestIndexedAnyOf(t *testing.T) {
if !reflect.DeepEqual(segments, test.segments) { if !reflect.DeepEqual(segments, test.segments) {
t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, 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: case Container:
fmt.Fprintf(buf, `"%s"[label="*AnyOf"];`, id) fmt.Fprintf(buf, `"%s"[label="Container(%T)"];`, id, m)
for _, m := range v.Content() { v.Content(func(m Matcher) {
rnd := rand.Int63() rnd := rand.Int63()
fmt.Fprintf(buf, graphviz(m, fmt.Sprintf("%x", rnd))) fmt.Fprintf(buf, graphviz(m, fmt.Sprintf("%x", rnd)))
fmt.Fprintf(buf, `"%s"->"%x";`, id, 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: default:
fmt.Fprintf(buf, `"%s"[label="%s"];`, id, m) fmt.Fprintf(buf, `"%s"[label="%s"];`, id, m)

View File

@ -10,7 +10,10 @@ type EveryOf struct {
} }
func NewEveryOf(ms []Matcher) Matcher { func NewEveryOf(ms []Matcher) Matcher {
e := EveryOf{ms, minLen(ms)} e := EveryOf{
ms: ms,
min: maxLen(ms),
}
if mis, ok := MatchIndexers(ms); ok { if mis, ok := MatchIndexers(ms); ok {
return IndexedEveryOf{e, mis} return IndexedEveryOf{e, mis}
} }
@ -30,6 +33,12 @@ func (e EveryOf) Match(s string) bool {
return true return true
} }
func (e EveryOf) Content(cb func(Matcher)) {
for _, m := range e.ms {
cb(m)
}
}
func (e EveryOf) String() string { func (e EveryOf) String() string {
return fmt.Sprintf("<every_of:[%s]>", e.ms) return fmt.Sprintf("<every_of:[%s]>", e.ms)
} }

View File

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

View File

@ -3,23 +3,27 @@ package match
import ( import (
"fmt" "fmt"
"github.com/gobwas/glob/internal/debug"
"github.com/gobwas/glob/util/runes" "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) { switch v := m.(type) {
case Any: case Any:
if len(v.sep) == 0 { if len(v.sep) == 0 {
return NewSuper() return NewSuper()
} }
case Container:
ms := v.Content()
if len(ms) == 1 {
return ms[0]
}
return m
case List: case List:
if v.not == false && len(v.rs) == 1 { if v.not == false && len(v.rs) == 1 {
return NewText(string(v.rs)) return NewText(string(v.rs))
@ -73,12 +77,33 @@ func Optimize(m Matcher) Matcher {
case leftNil && rightAny: case leftNil && rightAny:
return NewPrefixAny(txt.s, ra.sep) 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 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 { if len(ms) == 0 {
return nil, fmt.Errorf("compile error: need at least one matcher") return nil, fmt.Errorf("compile error: need at least one matcher")
} }
@ -90,33 +115,40 @@ func Compile(ms []Matcher) (Matcher, error) {
} }
var ( var (
idx = -1 x = -1
maxLen = -2 max = -2
wantText bool
indexer MatchIndexer indexer MatchIndexer
) )
for i, m := range ms { for i, m := range ms {
mi, ok := m.(MatchIndexer) mx, ok := m.(MatchIndexer)
if !ok { if !ok {
continue continue
} }
if n := m.MinLen(); n > maxLen { _, isText := m.(Text)
maxLen = n if wantText && !isText {
idx = i continue
indexer = mi }
n := m.MinLen()
if (!wantText && isText) || n > max {
max = n
x = i
indexer = mx
wantText = isText
} }
} }
if indexer == nil { if indexer == nil {
return nil, fmt.Errorf("can not index on matchers") return nil, fmt.Errorf("can not index on matchers")
} }
left := ms[:idx] left := ms[:x]
var right []Matcher var right []Matcher
if len(ms) > idx+1 { if len(ms) > x+1 {
right = ms[idx+1:] right = ms[x+1:]
} }
var l, r Matcher var l, r Matcher
var err error
if len(left) > 0 { if len(left) > 0 {
l, err = Compile(left) l, err = Compile(left)
if err != nil { if err != nil {
@ -239,40 +271,139 @@ func glueMatchersAsEvery(ms []Matcher) Matcher {
return NewEveryOf(every) return NewEveryOf(every)
} }
func Minimize(ms []Matcher) []Matcher { type result struct {
var ( ms []Matcher
result Matcher matchers int
left int minLen int
right int nesting int
count 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
}
}
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,
) )
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 { if best == nil {
result = glued best = new(result)
left = l }
right = r if best.ms == nil || compareResult(r, *best) < 0 {
count = r - l *best = r
if debug.Enabled {
debug.Logf("new best result")
} }
} }
best = minimize(cp, 0, 0, best)
if debug.Enabled {
debug.LeavePrefix()
} }
} }
if result == nil { 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 ms
} }
next := append(append([]Matcher{}, ms[:left]...), result) return best.ms
if right < len(ms) {
next = append(next, ms[right:]...)
}
if len(next) == len(ms) {
return next
}
return Minimize(next)
} }

View File

@ -3,12 +3,12 @@ package match
import ( import (
"reflect" "reflect"
"testing" "testing"
"github.com/gobwas/glob/match"
) )
var separators = []rune{'.'}
func TestCompile(t *testing.T) { func TestCompile(t *testing.T) {
for id, test := range []struct { for _, test := range []struct {
in []Matcher in []Matcher
exp Matcher exp Matcher
}{ }{
@ -26,7 +26,7 @@ func TestCompile(t *testing.T) {
}, },
NewEveryOf([]Matcher{ NewEveryOf([]Matcher{
NewMin(1), NewMin(1),
NewContains(string(separators)), NewAny(separators),
}), }),
}, },
{ {
@ -47,7 +47,7 @@ func TestCompile(t *testing.T) {
}, },
NewEveryOf([]Matcher{ NewEveryOf([]Matcher{
NewMin(1), NewMin(1),
NewContains("a"), NewAny([]rune{'a'}),
}), }),
}, },
{ {
@ -58,7 +58,7 @@ func TestCompile(t *testing.T) {
}, },
NewTree( NewTree(
NewText("c"), NewText("c"),
NewBTree( NewTree(
NewSingle(separators), NewSingle(separators),
NewSuper(), NewSuper(),
nil, nil,
@ -93,71 +93,73 @@ func TestCompile(t *testing.T) {
}), }),
}, },
} { } {
t.Run("", func(t *testing.T) {
act, err := Compile(test.in) act, err := Compile(test.in)
if err != nil { if err != nil {
t.Errorf("#%d compile matchers error: %s", id, err) t.Fatalf("Compile() error: %s", err)
continue
} }
if !reflect.DeepEqual(act, test.exp) { if !reflect.DeepEqual(act, test.exp) {
t.Errorf("#%d unexpected compile matchers result:\nact: %#v;\nexp: %#v", id, act, test.exp) t.Errorf(
continue "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) { func TestMinimize(t *testing.T) {
for id, test := range []struct { for _, test := range []struct {
in, exp []match.Matcher in, exp []Matcher
}{ }{
{ {
[]match.Matcher{ in: []Matcher{
match.NewRange('a', 'c', true), NewRange('a', 'c', true),
match.NewList([]rune{'z', 't', 'e'}, false), NewList([]rune{'z', 't', 'e'}, false),
match.NewText("c"), NewText("c"),
match.NewSingle(nil), NewSingle(nil),
match.NewAny(nil), NewAny(nil),
}, },
[]match.Matcher{ exp: []Matcher{
match.NewRow( NewRow([]MatchIndexSizer{
4, NewRange('a', 'c', true),
[]match.Matcher{ NewList([]rune{'z', 't', 'e'}, false),
match.NewRange('a', 'c', true), NewText("c"),
match.NewList([]rune{'z', 't', 'e'}, false), }),
match.NewText("c"), NewMin(1),
match.NewSingle(nil),
}...,
),
match.NewAny(nil),
}, },
}, },
{ {
[]match.Matcher{ in: []Matcher{
match.NewRange('a', 'c', true), NewRange('a', 'c', true),
match.NewList([]rune{'z', 't', 'e'}, false), NewList([]rune{'z', 't', 'e'}, false),
match.NewText("c"), NewText("c"),
match.NewSingle(nil), NewSingle(nil),
match.NewAny(nil), NewAny(nil),
match.NewSingle(nil), NewSingle(nil),
match.NewSingle(nil), NewSingle(nil),
match.NewAny(nil), NewAny(nil),
}, },
[]match.Matcher{ exp: []Matcher{
match.NewRow( NewRow([]MatchIndexSizer{
3, NewRange('a', 'c', true),
match.Matchers{ NewList([]rune{'z', 't', 'e'}, false),
match.NewRange('a', 'c', true), NewText("c"),
match.NewList([]rune{'z', 't', 'e'}, false), }),
match.NewText("c"), NewMin(3),
}...,
),
match.NewMin(3),
}, },
}, },
} { } {
act := minimizeMatchers(test.in) t.Run("", func(t *testing.T) {
act := Minimize(test.in)
if !reflect.DeepEqual(act, test.exp) { if !reflect.DeepEqual(act, test.exp) {
t.Errorf("#%d unexpected convert matchers 2 result:\nact: %#v\nexp: %#v", id, act, test.exp) t.Errorf(
continue "Minimize():\nact: %#v;\nexp: %#v",
} act, test.exp,
)
}
})
} }
} }

View File

@ -3,6 +3,8 @@ package match
import ( import (
"fmt" "fmt"
"unicode/utf8" "unicode/utf8"
"github.com/gobwas/glob/internal/debug"
) )
type Range struct { type Range struct {
@ -22,7 +24,11 @@ func (self Range) RunesCount() int {
return 1 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) r, w := utf8.DecodeRuneInString(s)
if len(s) > w { if len(s) > w {
return false return false
@ -33,7 +39,11 @@ func (self Range) Match(s string) bool {
return inRange == !self.Not 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 { for i, r := range s {
if self.Not != (r >= self.Lo && r <= self.Hi) { if self.Not != (r >= self.Lo && r <= self.Hi) {
return i, segmentsByRuneLength[utf8.RuneLen(r)] return i, segmentsByRuneLength[utf8.RuneLen(r)]

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"unicode/utf8" "unicode/utf8"
"github.com/gobwas/glob/internal/debug"
"github.com/gobwas/glob/util/runes" "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) { if !runes.ExactlyRunesCount(s, r.runes) {
return false return false
} }
@ -40,8 +45,14 @@ func (r Row) RunesCount() int {
return r.runes return r.runes
} }
func (r Row) Index(s string) (int, []int) { func (r Row) Index(s string) (index int, segments []int) {
for j := 0; j < len(s)-r.runes; { 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:]) i, _ := r.ms[0].Index(s[j:])
if i == -1 { if i == -1 {
return -1, nil return -1, nil
@ -55,8 +66,14 @@ func (r Row) Index(s string) (int, []int) {
return -1, nil return -1, nil
} }
func (r Row) Content(cb func(Matcher)) {
for _, m := range r.ms {
cb(m)
}
}
func (r Row) String() string { 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 { 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 { func (s Single) String() string {
if len(s.sep) == 0 {
return "<single>"
}
return fmt.Sprintf("<single:![%s]>", string(s.sep)) return fmt.Sprintf("<single:![%s]>", string(s.sep))
} }

View File

@ -74,29 +74,32 @@ func (t Tree) MinLen() int {
return t.minLen 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) { func (t Tree) Match(s string) (ok bool) {
if debug.Enabled { if debug.Enabled {
debug.Enter() done := debug.Matching("tree", s)
debug.Logf("tree: matching %q: %v", s, t) defer func() { done(ok) }()
defer func(s string) {
debug.Logf("tree: result: %q -> %v", s, ok)
debug.Leave()
}(s)
} }
offset, limit := t.offsetLimit(s) offset, limit := t.offsetLimit(s)
q := s[offset : len(s)-limit] q := s[offset : len(s)-limit]
if debug.Enabled { 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 { for len(q) >= t.vrunes {
// search for matching part in substring // search for matching part in substring
index, segments := t.value.Index(q) index, segments := t.value.Index(q)
if debug.Enabled {
debug.Logf("tree: index #%d %q (%v)", index, q, t.value)
}
if index == -1 { if index == -1 {
releaseSegments(segments) releaseSegments(segments)
return false return false
@ -110,7 +113,7 @@ func (t Tree) Match(s string) (ok bool) {
left = l == "" left = l == ""
} }
if debug.Enabled { if debug.Enabled {
debug.Logf("tree: left %q %v", l, left) debug.Logf("left %q: -> %t", l, left)
} }
if left { if left {
for _, seg := range segments { for _, seg := range segments {
@ -124,7 +127,7 @@ func (t Tree) Match(s string) (ok bool) {
right = r == "" right = r == ""
} }
if debug.Enabled { if debug.Enabled {
debug.Logf("tree: right %q %v", r, right) debug.Logf("right %q: -> %t", r, right)
} }
if right { if right {
releaseSegments(segments) releaseSegments(segments)

View File

@ -9,3 +9,13 @@ func minLen(ms []Matcher) (min int) {
} }
return min 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
}