diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index f0ebe69..13a765f 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -5,190 +5,11 @@ import ( "testing" "github.com/gobwas/glob/match" - "github.com/gobwas/glob/match/debug" "github.com/gobwas/glob/syntax/ast" ) var separators = []rune{'.'} -func TestGlueMatchers(t *testing.T) { - for id, test := range []struct { - in []match.Matcher - exp match.Matcher - }{ - { - []match.Matcher{ - match.NewSuper(), - match.NewSingle(nil), - }, - match.NewMin(1), - }, - { - []match.Matcher{ - match.NewAny(separators), - match.NewSingle(separators), - }, - match.EveryOf{match.Matchers{ - match.NewMin(1), - match.NewContains(string(separators), true), - }}, - }, - { - []match.Matcher{ - match.NewSingle(nil), - match.NewSingle(nil), - match.NewSingle(nil), - }, - match.EveryOf{match.Matchers{ - match.NewMin(3), - match.NewMax(3), - }}, - }, - { - []match.Matcher{ - match.NewList([]rune{'a'}, true), - match.NewAny([]rune{'a'}), - }, - match.EveryOf{match.Matchers{ - match.NewMin(1), - match.NewContains("a", true), - }}, - }, - } { - act, err := compileMatchers(test.in) - if err != nil { - t.Errorf("#%d convert matchers error: %s", id, err) - continue - } - - if !reflect.DeepEqual(act, test.exp) { - t.Errorf("#%d unexpected convert matchers result:\nact: %#v;\nexp: %#v", id, act, test.exp) - continue - } - } -} - -func TestCompileMatchers(t *testing.T) { - for id, test := range []struct { - in []match.Matcher - exp match.Matcher - }{ - { - []match.Matcher{ - match.NewSuper(), - match.NewSingle(separators), - match.NewText("c"), - }, - match.NewBTree( - match.NewText("c"), - match.NewBTree( - match.NewSingle(separators), - match.NewSuper(), - nil, - ), - nil, - ), - }, - { - []match.Matcher{ - match.NewAny(nil), - match.NewText("c"), - match.NewAny(nil), - }, - match.NewBTree( - match.NewText("c"), - match.NewAny(nil), - match.NewAny(nil), - ), - }, - { - []match.Matcher{ - match.NewRange('a', 'c', true), - match.NewList([]rune{'z', 't', 'e'}, false), - match.NewText("c"), - match.NewSingle(nil), - }, - match.NewRow( - 4, - match.Matchers{ - match.NewRange('a', 'c', true), - match.NewList([]rune{'z', 't', 'e'}, false), - match.NewText("c"), - match.NewSingle(nil), - }..., - ), - }, - } { - act, err := compileMatchers(test.in) - if err != nil { - t.Errorf("#%d convert matchers error: %s", id, err) - continue - } - - if !reflect.DeepEqual(act, test.exp) { - t.Errorf("#%d unexpected convert matchers result:\nact: %#v\nexp: %#v", id, act, test.exp) - continue - } - } -} - -func TestConvertMatchers(t *testing.T) { - for id, test := range []struct { - in, exp []match.Matcher - }{ - { - []match.Matcher{ - match.NewRange('a', 'c', true), - match.NewList([]rune{'z', 't', 'e'}, false), - match.NewText("c"), - match.NewSingle(nil), - match.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), - }, - }, - { - []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), - }, - []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), - }, - }, - } { - 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 - } - } -} - func TestCompiler(t *testing.T) { for id, test := range []struct { ast *ast.Node @@ -254,10 +75,10 @@ func TestCompiler(t *testing.T) { ast.NewNode(ast.KindSingle, nil), ), sep: separators, - result: match.EveryOf{Matchers: match.Matchers{ + result: match.NewEveryOf([]match.Matcher{ match.NewMin(3), - match.NewContains(string(separators), true), - }}, + match.NewAny(separators), + }), }, { ast: ast.NewNode(ast.KindPattern, nil, @@ -275,14 +96,11 @@ func TestCompiler(t *testing.T) { ast.NewNode(ast.KindSingle, nil), ), sep: separators, - result: match.NewBTree( - match.NewRow( - 4, - match.Matchers{ - match.NewText("abc"), - match.NewSingle(separators), - }..., - ), + result: match.NewTree( + match.NewRow([]match.MatchIndexSizer{ + match.NewText("abc"), + match.NewSingle(separators), + }), match.NewAny(separators), nil, ), @@ -297,11 +115,14 @@ func TestCompiler(t *testing.T) { ast.NewNode(ast.KindSuper, nil), ), sep: separators, - result: match.NewBTree( + result: match.NewTree( match.NewText("/"), nil, - match.NewBTree( - match.NewAnyOf(match.NewText("z"), match.NewText("ab")), + match.NewTree( + match.MustIndexedAnyOf( + match.NewText("z"), + match.NewText("ab"), + ), nil, match.NewSuper(), ), @@ -315,15 +136,12 @@ func TestCompiler(t *testing.T) { ast.NewNode(ast.KindSingle, nil), ), sep: separators, - result: match.NewBTree( - match.NewRow( - 5, - match.Matchers{ - match.NewSingle(separators), - match.NewText("abc"), - match.NewSingle(separators), - }..., - ), + result: match.NewTree( + match.NewRow([]match.MatchIndexSizer{ + match.NewSingle(separators), + match.NewText("abc"), + match.NewSingle(separators), + }), match.NewSuper(), nil, ), @@ -359,7 +177,7 @@ func TestCompiler(t *testing.T) { ast.NewNode(ast.KindAny, nil), ast.NewNode(ast.KindAny, nil), ), - result: match.NewContains("abc", false), + result: match.NewContains("abc"), }, { ast: ast.NewNode(ast.KindPattern, nil, @@ -371,13 +189,14 @@ func TestCompiler(t *testing.T) { ast.NewNode(ast.KindAny, nil), ), sep: separators, - result: match.NewBTree( + result: match.NewTree( match.NewText("abc"), match.NewAny(separators), match.NewAny(separators), ), }, { + // TODO: THIS! ast: ast.NewNode(ast.KindPattern, nil, ast.NewNode(ast.KindSuper, nil), ast.NewNode(ast.KindSingle, nil), @@ -385,7 +204,7 @@ func TestCompiler(t *testing.T) { ast.NewNode(ast.KindSuper, nil), ast.NewNode(ast.KindSingle, nil), ), - result: match.NewBTree( + result: match.NewTree( match.NewText("abc"), match.NewMin(1), match.NewMin(1), @@ -430,14 +249,14 @@ func TestCompiler(t *testing.T) { ), ), ), - result: match.NewBTree( + result: match.NewTree( match.NewText("abc"), nil, - match.AnyOf{Matchers: match.Matchers{ + match.NewAnyOf( match.NewSingle(nil), match.NewList([]rune{'d', 'e', 'f'}, false), match.NewNothing(), - }}, + ), ), }, { @@ -446,14 +265,11 @@ func TestCompiler(t *testing.T) { ast.NewNode(ast.KindRange, ast.Range{Lo: 'a', Hi: 'x', Not: true}), ast.NewNode(ast.KindAny, nil), ), - result: match.NewBTree( - match.NewRow( - 2, - match.Matchers{ - match.NewRange('a', 'z', false), - match.NewRange('a', 'x', true), - }..., - ), + result: match.NewTree( + match.NewRow([]match.MatchIndexSizer{ + match.NewRange('a', 'z', false), + match.NewRange('a', 'x', true), + }), nil, match.NewSuper(), ), @@ -473,17 +289,14 @@ func TestCompiler(t *testing.T) { ), ), ), - result: match.NewRow( - 7, - match.Matchers{ - match.NewText("abc"), - match.AnyOf{Matchers: match.Matchers{ - match.NewList([]rune{'a', 'b', 'c'}, false), - match.NewList([]rune{'d', 'e', 'f'}, false), - }}, - match.NewText("ghi"), - }..., - ), + result: match.NewRow([]match.MatchIndexSizer{ + match.NewText("abc"), + match.MustIndexedSizedAnyOf( + match.NewList([]rune{'a', 'b', 'c'}, false), + match.NewList([]rune{'d', 'e', 'f'}, false), + ), + match.NewText("ghi"), + }), }, } { m, err := Compile(test.ast, test.sep) @@ -493,7 +306,7 @@ func TestCompiler(t *testing.T) { } 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, debug.Graphviz("", test.result.(match.Matcher)), debug.Graphviz("", m.(match.Matcher))) + 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 } } diff --git a/match/any_of.go b/match/any_of.go index 490c63d..2010584 100644 --- a/match/any_of.go +++ b/match/any_of.go @@ -12,11 +12,35 @@ type AnyOf struct { func NewAnyOf(ms ...Matcher) Matcher { a := AnyOf{ms, minLen(ms)} if mis, ok := MatchIndexers(ms); ok { - return IndexedAnyOf{a, mis} + x := IndexedAnyOf{a, mis} + if msz, ok := MatchIndexSizers(ms); ok { + sz := -1 + for _, m := range msz { + n := m.RunesCount() + if sz == -1 { + sz = n + } else if sz != n { + sz = -1 + break + } + } + if sz != -1 { + return IndexedSizedAnyOf{x, sz} + } + } + return x } return a } +func MustIndexedAnyOf(ms ...Matcher) MatchIndexer { + return NewAnyOf(ms...).(MatchIndexer) +} + +func MustIndexedSizedAnyOf(ms ...Matcher) MatchIndexSizer { + return NewAnyOf(ms...).(MatchIndexSizer) +} + func (a AnyOf) Match(s string) bool { for _, m := range a.ms { if m.Match(s) { @@ -72,3 +96,12 @@ func (a IndexedAnyOf) Index(s string) (int, []int) { func (a IndexedAnyOf) String() string { return fmt.Sprintf("", a.ms) } + +type IndexedSizedAnyOf struct { + IndexedAnyOf + runes int +} + +func (a IndexedSizedAnyOf) RunesCount() int { + return a.runes +} diff --git a/match/contains.go b/match/contains.go index 1178885..2b241c8 100644 --- a/match/contains.go +++ b/match/contains.go @@ -14,6 +14,10 @@ func NewContains(needle string) Contains { return Contains{needle, false} } +func NewNotContains(needle string) Contains { + return Contains{needle, true} +} + func (c Contains) Match(s string) bool { return strings.Contains(s, c.s) != c.not } diff --git a/match/list.go b/match/list.go index 296bbdb..03abf27 100644 --- a/match/list.go +++ b/match/list.go @@ -30,6 +30,10 @@ func (l List) MinLen() int { return 1 } +func (l List) RunesCount() int { + return 1 +} + func (l List) Index(s string) (int, []int) { for i, r := range s { if l.not == (runes.IndexRune(l.rs, r) == -1) { diff --git a/match/match.go b/match/match.go index bfc401c..71536b2 100644 --- a/match/match.go +++ b/match/match.go @@ -42,15 +42,28 @@ type Container interface { func MatchIndexers(ms []Matcher) ([]MatchIndexer, bool) { for _, m := range ms { - if _, ok := m.(Indexer); !ok { + if _, ok := m.(MatchIndexer); !ok { return nil, false } } - mis := make([]MatchIndexer, len(ms)) - for i := range mis { - mis[i] = ms[i].(MatchIndexer) + r := make([]MatchIndexer, len(ms)) + for i := range r { + r[i] = ms[i].(MatchIndexer) } - return mis, true + return r, true +} + +func MatchIndexSizers(ms []Matcher) ([]MatchIndexSizer, bool) { + for _, m := range ms { + if _, ok := m.(MatchIndexSizer); !ok { + return nil, false + } + } + r := make([]MatchIndexSizer, len(ms)) + for i := range r { + r[i] = ms[i].(MatchIndexSizer) + } + return r, true } type Matchers []Matcher diff --git a/match/optimize_test.go b/match/optimize_test.go new file mode 100644 index 0000000..80408eb --- /dev/null +++ b/match/optimize_test.go @@ -0,0 +1,163 @@ +package match + +import ( + "reflect" + "testing" + + "github.com/gobwas/glob/match" +) + +func TestCompile(t *testing.T) { + for id, test := range []struct { + in []Matcher + exp Matcher + }{ + { + []Matcher{ + NewSuper(), + NewSingle(nil), + }, + NewMin(1), + }, + { + []Matcher{ + NewAny(separators), + NewSingle(separators), + }, + NewEveryOf([]Matcher{ + NewMin(1), + NewContains(string(separators)), + }), + }, + { + []Matcher{ + NewSingle(nil), + NewSingle(nil), + NewSingle(nil), + }, + NewEveryOf([]Matcher{ + NewMin(3), + NewMax(3), + }), + }, + { + []Matcher{ + NewList([]rune{'a'}, true), + NewAny([]rune{'a'}), + }, + NewEveryOf([]Matcher{ + NewMin(1), + NewContains("a"), + }), + }, + { + []Matcher{ + NewSuper(), + NewSingle(separators), + NewText("c"), + }, + NewTree( + NewText("c"), + NewBTree( + NewSingle(separators), + NewSuper(), + nil, + ), + nil, + ), + }, + { + []Matcher{ + NewAny(nil), + NewText("c"), + NewAny(nil), + }, + NewTree( + NewText("c"), + NewAny(nil), + NewAny(nil), + ), + }, + { + []Matcher{ + NewRange('a', 'c', true), + NewList([]rune{'z', 't', 'e'}, false), + NewText("c"), + NewSingle(nil), + }, + NewRow([]MatchIndexSizer{ + NewRange('a', 'c', true), + NewList([]rune{'z', 't', 'e'}, false), + NewText("c"), + NewSingle(nil), + }), + }, + } { + 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 + } + } +} + +func TestMinimize(t *testing.T) { + for id, test := range []struct { + in, exp []match.Matcher + }{ + { + []match.Matcher{ + match.NewRange('a', 'c', true), + match.NewList([]rune{'z', 't', 'e'}, false), + match.NewText("c"), + match.NewSingle(nil), + match.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), + }, + }, + { + []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), + }, + []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), + }, + }, + } { + 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 + } + } +} diff --git a/match/range.go b/match/range.go index da4e940..e3120ac 100644 --- a/match/range.go +++ b/match/range.go @@ -18,6 +18,10 @@ func (self Range) MinLen() int { return 1 } +func (self Range) RunesCount() int { + return 1 +} + func (self Range) Match(s string) bool { r, w := utf8.DecodeRuneInString(s) if len(s) > w {