forked from mirror/glob
wip
This commit is contained in:
parent
abc7140723
commit
4a52abd846
|
@ -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:
|
||||
|
|
|
@ -11,44 +11,51 @@ import (
|
|||
var separators = []rune{'.'}
|
||||
|
||||
func TestCompiler(t *testing.T) {
|
||||
for id, test := range []struct {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
ast *ast.Node
|
||||
result match.Matcher
|
||||
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),
|
||||
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),
|
||||
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)
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
act, 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.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)),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,14 @@ package debug
|
|||
|
||||
const Enabled = false
|
||||
|
||||
func Logf(_ string, _ ...interface{}) {}
|
||||
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")
|
||||
}
|
||||
|
|
|
@ -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--
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -41,7 +41,8 @@ func TestIndexedAnyOf(t *testing.T) {
|
|||
[]int{1},
|
||||
},
|
||||
} {
|
||||
a := NewAnyOf(test.matchers...).(IndexedAnyOf)
|
||||
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)
|
||||
|
@ -49,5 +50,6 @@ func TestIndexedAnyOf(t *testing.T) {
|
|||
if !reflect.DeepEqual(segments, test.segments) {
|
||||
t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ type MatchIndexSizer interface {
|
|||
}
|
||||
|
||||
type Container interface {
|
||||
Content() []Matcher
|
||||
Content(func(Matcher))
|
||||
}
|
||||
|
||||
func MatchIndexers(ms []Matcher) ([]MatchIndexer, bool) {
|
||||
|
|
|
@ -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
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
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 {
|
||||
result = glued
|
||||
left = l
|
||||
right = r
|
||||
count = r - l
|
||||
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()
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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) {
|
|||
}),
|
||||
},
|
||||
} {
|
||||
t.Run("", func(t *testing.T) {
|
||||
act, err := Compile(test.in)
|
||||
if err != nil {
|
||||
t.Errorf("#%d compile matchers error: %s", id, err)
|
||||
continue
|
||||
t.Fatalf("Compile() error: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(act, test.exp) {
|
||||
t.Errorf("#%d unexpected compile matchers result:\nact: %#v;\nexp: %#v", id, act, test.exp)
|
||||
continue
|
||||
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)
|
||||
t.Run("", func(t *testing.T) {
|
||||
act := Minimize(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.Errorf(
|
||||
"Minimize():\nact: %#v;\nexp: %#v",
|
||||
act, test.exp,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
25
match/row.go
25
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("<row_%d:[%s]>", r.runes, r.ms)
|
||||
return fmt.Sprintf("<row_%d:%s>", r.runes, r.ms)
|
||||
}
|
||||
|
||||
func (r Row) matchAll(s string) bool {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue