diff --git a/.gitignore b/.gitignore index b4ae623..a3730bf 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ glob.iml *.dot *.png *.svg +patterns.txt diff --git a/bench.sh b/bench.sh index 0f4722c..e039299 100755 --- a/bench.sh +++ b/bench.sh @@ -1,6 +1,6 @@ #! /bin/bash -rnd=$(head -c4 0 { + *offset-- + fmt.Println("skipped") + continue + } + patterns = append(patterns, s.Text()) + } + file.Close() + } + if len(patterns) == 0 { + return } var separators []rune if len(*sep) > 0 { for _, c := range strings.Split(*sep, ",") { - if r, w := utf8.DecodeRuneInString(c); len(c) > w { - fmt.Println("only single charactered separators are allowed") + r, w := utf8.DecodeRuneInString(c) + if len(c) > w { + fmt.Println("only single charactered separators are allowed: %+q", c) os.Exit(1) - } else { - separators = append(separators, r) } + separators = append(separators, r) } } - glob, err := glob.Compile(*pattern, separators...) - if err != nil { - fmt.Println("could not compile pattern:", err) - os.Exit(1) + br := bufio.NewReader(os.Stdin) + for _, p := range patterns { + g, err := glob.Compile(p, separators...) + if err != nil { + fmt.Printf("could not compile pattern %+q: %v\n", p, err) + os.Exit(1) + } + s := match.Graphviz(p, g.(match.Matcher)) + if *auto { + fmt.Fprintf(os.Stdout, "pattern: %+q: ", p) + if err := open(s); err != nil { + fmt.Printf("could not open graphviz: %v", err) + os.Exit(1) + } + if !next(br) { + return + } + } else { + fmt.Fprintln(os.Stdout, s) + } } - - matcher := glob.(match.Matcher) - fmt.Fprint(os.Stdout, match.Graphviz(*pattern, matcher)) +} + +func open(s string) error { + file, err := os.Create("glob.graphviz.png") + if err != nil { + return err + } + defer file.Close() + cmd := exec.Command("dot", "-Tpng") + cmd.Stdin = strings.NewReader(s) + cmd.Stdout = file + if err := cmd.Run(); err != nil { + return err + } + if err := file.Sync(); err != nil { + return err + } + cmd = exec.Command("open", file.Name()) + return cmd.Run() +} + +func next(in *bufio.Reader) bool { + fmt.Fprint(os.Stdout, "cancel? [Y/n]: ") + p, err := in.ReadBytes('\n') + if err != nil { + return false + } + if p[0] == 'Y' { + return false + } + return true } diff --git a/glob_test.go b/glob_test.go index c93d263..4501feb 100644 --- a/glob_test.go +++ b/glob_test.go @@ -1,8 +1,11 @@ package glob import ( + "fmt" "regexp" "testing" + + "github.com/gobwas/glob/match" ) const ( @@ -57,7 +60,12 @@ type test struct { } func glob(s bool, p, m string, d ...rune) test { - return test{p, m, s, d} + return test{ + should: s, + pattern: p, + match: m, + delimiters: d, + } } func globc(p string, d ...rune) test { @@ -245,20 +253,10 @@ func BenchmarkParseRegexp(b *testing.B) { func BenchmarkAllGlobMatch(b *testing.B) { m, _ := Compile(pattern_all) - for i := 0; i < b.N; i++ { _ = m.Match(fixture_all_match) } } -func BenchmarkAllGlobMatchParallel(b *testing.B) { - m, _ := Compile(pattern_all) - - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - _ = m.Match(fixture_all_match) - } - }) -} func BenchmarkAllRegexpMatch(b *testing.B) { m := regexp.MustCompile(regexp_all) @@ -268,22 +266,24 @@ func BenchmarkAllRegexpMatch(b *testing.B) { _ = m.Match(f) } } + +func TestAllGlobMismatch(t *testing.T) { + m := MustCompile(pattern_all) + fmt.Println("====") + fmt.Println(match.Graphviz(pattern_all, m.(match.Matcher))) + fmt.Println("====") + m.Match(fixture_all_mismatch) +} + func BenchmarkAllGlobMismatch(b *testing.B) { - m, _ := Compile(pattern_all) + m := MustCompile(pattern_all) + fmt.Println(match.Graphviz(pattern_all, m.(match.Matcher))) for i := 0; i < b.N; i++ { _ = m.Match(fixture_all_mismatch) } } -func BenchmarkAllGlobMismatchParallel(b *testing.B) { - m, _ := Compile(pattern_all) - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - _ = m.Match(fixture_all_mismatch) - } - }) -} func BenchmarkAllRegexpMismatch(b *testing.B) { m := regexp.MustCompile(regexp_all) f := []byte(fixture_all_mismatch) diff --git a/match/optimize.go b/match/optimize.go index 15f7be2..6b998e1 100644 --- a/match/optimize.go +++ b/match/optimize.go @@ -148,21 +148,22 @@ func Compile(ms []Matcher) (m Matcher, err error) { right = ms[x+1:] } - var l, r Matcher + var ( + l Matcher = Nothing{} + r Matcher = Nothing{} + ) if len(left) > 0 { l, err = Compile(left) if err != nil { return nil, err } } - if len(right) > 0 { r, err = Compile(right) if err != nil { return nil, err } } - return NewTree(indexer, l, r), nil } @@ -279,16 +280,16 @@ type result struct { } func compareResult(a, b result) int { - if x := len(a.ms) - len(b.ms); x != 0 { + if x := b.minLen - a.minLen; x != 0 { return x } if x := a.matchers - b.matchers; x != 0 { return x } - if x := b.minLen - a.minLen; x != 0 { + if x := a.nesting - b.nesting; x != 0 { return x } - if x := a.nesting - b.nesting; x != 0 { + if x := len(a.ms) - len(b.ms); x != 0 { return x } return 0 diff --git a/match/tree.go b/match/tree.go index 8173cc1..c0a3ac2 100644 --- a/match/tree.go +++ b/match/tree.go @@ -25,6 +25,12 @@ type SizedTree struct { Tree } +type IndexedTree struct { + value MatchIndexer + left MatchIndexer + right MatchIndexer +} + func (st SizedTree) RunesCount() int { return st.Tree.runes } @@ -49,24 +55,19 @@ func NewTree(v MatchIndexer, l, r Matcher) Matcher { ) if lsz { tree.lrunes = ls.RunesCount() - } else { - tree.lrunes = -1 } if rsz { tree.rrunes = rs.RunesCount() - } else { - tree.rrunes = -1 } if vsz { tree.vrunes = vs.RunesCount() - } else { - tree.vrunes = -1 } + //li, lix := l.(MatchIndexer) + //ri, rix := r.(MatchIndexer) if vsz && lsz && rsz { tree.runes = tree.vrunes + tree.lrunes + tree.rrunes return SizedTree{tree} } - tree.runes = -1 return tree } @@ -90,44 +91,44 @@ func (t Tree) Match(s string) (ok bool) { defer func() { done(ok) }() } + n := len(s) offset, limit := t.offsetLimit(s) - q := s[offset : len(s)-limit] - if debug.Enabled { - 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) + for len(s)-offset-limit >= t.vrunes { + if debug.Enabled { + debug.Logf( + "value %s indexing: %q (offset=%d; limit=%d)", + t.value, s[offset:n-limit], offset, limit, + ) + } + index, segments := t.value.Index(s[offset : n-limit]) + if debug.Enabled { + debug.Logf( + "value %s index: %d; %v", + t.value, index, segments, + ) + } if index == -1 { releaseSegments(segments) return false } - l := s[:offset+index] - var left bool - if t.left != nil { - left = t.left.Match(l) - } else { - left = l == "" - } if debug.Enabled { - debug.Logf("left %q: -> %t", l, left) + debug.Logf("matching left: %q", s[:offset+index]) } + left := t.left.Match(s[:offset+index]) + if debug.Enabled { + debug.Logf("matching left: -> %t", left) + } + if left { for _, seg := range segments { - var ( - right bool - ) - r := s[offset+index+seg:] - if t.right != nil { - right = t.right.Match(r) - } else { - right = r == "" - } if debug.Enabled { - debug.Logf("right %q: -> %t", r, right) + debug.Logf("matching right: %q", s[offset+index+seg:]) + } + right := t.right.Match(s[offset+index+seg:]) + if debug.Enabled { + debug.Logf("matching right: -> %t", right) } if right { releaseSegments(segments) @@ -136,13 +137,14 @@ func (t Tree) Match(s string) (ok bool) { } } - _, x := utf8.DecodeRuneInString(q[index:]) releaseSegments(segments) - q = q[x:] - offset += x - if debug.Enabled { - debug.Logf("tree: sliced to %q", q) + + _, x := utf8.DecodeRuneInString(s[offset+index:]) + if x == 0 { + // No progress. + break } + offset = offset + index + x } return false diff --git a/match/tree_test.go b/match/tree_test.go index 3e89287..7d263f5 100644 --- a/match/tree_test.go +++ b/match/tree_test.go @@ -11,6 +11,16 @@ func TestTree(t *testing.T) { str string exp bool }{ + { + NewTree(NewText("x"), NewText("y"), NewText("z")), + "0000x1111", + false, + }, + { + NewTree(NewText("a"), NewSingle(nil), NewSingle(nil)), + "aaa", + false, + }, { NewTree(NewText("abc"), NewSuper(), NewSuper()), "abc",