Merge pull request #12 from gobwas/bugfix-7

Bugfix
This commit is contained in:
Sergey Kamardin 2016-05-14 22:15:31 +03:00
commit 1a9b5d0057
8 changed files with 223 additions and 231 deletions

View File

@ -385,20 +385,25 @@ func compileMatchers(matchers []match.Matcher) (match.Matcher, error) {
return m, nil return m, nil
} }
var ( idx := -1
val match.Matcher
idx int
)
maxLen := -1 maxLen := -1
var val match.Matcher
for i, matcher := range matchers { for i, matcher := range matchers {
l := matcher.Len() if l := matcher.Len(); l != -1 && l >= maxLen {
if l >= maxLen {
maxLen = l maxLen = l
idx = i idx = i
val = matcher val = matcher
} }
} }
if val == nil { // not found matcher with static length
r, err := compileMatchers(matchers[1:])
if err != nil {
return nil, err
}
return match.NewBTree(matchers[0], nil, r), nil
}
left := matchers[:idx] left := matchers[:idx]
var right []match.Matcher var right []match.Matcher
if len(matchers) > idx+1 { if len(matchers) > idx+1 {
@ -424,74 +429,8 @@ func compileMatchers(matchers []match.Matcher) (match.Matcher, error) {
return match.NewBTree(val, l, r), nil return match.NewBTree(val, l, r), nil
} }
//func complexity(m match.Matcher) int {
// var matchers []match.Matcher
// var k int
//
// switch matcher := m.(type) {
//
// case match.Nothing:
// return 0
//
// case match.Max, match.Range, match.Suffix, match.Text:
// return 1
//
// case match.PrefixSuffix, match.Single, match.Row:
// return 2
//
// case match.Any, match.Contains, match.List, match.Min, match.Prefix, match.Super:
// return 4
//
// case match.BTree:
// matchers = append(matchers, matcher.Value)
// if matcher.Left != nil {
// matchers = append(matchers, matcher.Left)
// }
// if matcher.Right != nil {
// matchers = append(matchers, matcher.Right)
// }
// k = 1
//
// case match.AnyOf:
// matchers = matcher.Matchers
// k = 1
// case match.EveryOf:
// matchers = matcher.Matchers
// k = 1
//
// default:
// return 0
// }
//
// var sum int
// for _, m := range matchers {
// sum += complexity(m)
// }
//
// return sum * k
//}
func doAnyOf(n *nodeAnyOf, s []rune) (match.Matcher, error) {
var matchers []match.Matcher
for _, desc := range n.children() {
if desc == nil {
matchers = append(matchers, match.NewNothing())
continue
}
m, err := do(desc, s)
if err != nil {
return nil, err
}
matchers = append(matchers, optimize(m))
}
return match.NewAnyOf(matchers...), nil
}
func do(leaf node, s []rune) (m match.Matcher, err error) { func do(leaf node, s []rune) (m match.Matcher, err error) {
switch n := leaf.(type) { switch n := leaf.(type) {
case *nodeAnyOf: case *nodeAnyOf:
// todo this could be faster on pattern_alternatives_combine_lite // todo this could be faster on pattern_alternatives_combine_lite
if n := minimizeAnyOf(n.children()); n != nil { if n := minimizeAnyOf(n.children()); n != nil {
@ -559,120 +498,7 @@ func do(leaf node, s []rune) (m match.Matcher, err error) {
return optimize(m), nil return optimize(m), nil
} }
func do2(node node, s []rune) ([]match.Matcher, error) {
var result []match.Matcher
switch n := node.(type) {
case *nodePattern:
ways := [][]match.Matcher{[]match.Matcher{}}
for _, desc := range node.children() {
variants, err := do2(desc, s)
if err != nil {
return nil, err
}
fmt.Println("variants pat", variants)
for i, l := 0, len(ways); i < l; i++ {
for i := 0; i < len(variants); i++ {
o := optimize(variants[i])
if i == len(variants)-1 {
ways[i] = append(ways[i], o)
} else {
var w []match.Matcher
copy(w, ways[i])
ways = append(ways, append(w, o))
}
}
}
fmt.Println("ways pat", ways)
}
for _, matchers := range ways {
c, err := compileMatchers(minimizeMatchers(matchers))
if err != nil {
return nil, err
}
result = append(result, c)
}
case *nodeAnyOf:
ways := make([][]match.Matcher, len(node.children()))
for _, desc := range node.children() {
variants, err := do2(desc, s)
if err != nil {
return nil, err
}
fmt.Println("variants any", variants)
for x, l := 0, len(ways); x < l; x++ {
for i := 0; i < len(variants); i++ {
o := optimize(variants[i])
if i == len(variants)-1 {
ways[x] = append(ways[x], o)
} else {
var w []match.Matcher
copy(w, ways[x])
ways = append(ways, append(w, o))
}
}
}
fmt.Println("ways any", ways)
}
for _, matchers := range ways {
c, err := compileMatchers(minimizeMatchers(matchers))
if err != nil {
return nil, err
}
result = append(result, c)
}
case *nodeList:
result = append(result, match.NewList([]rune(n.chars), n.not))
case *nodeRange:
result = append(result, match.NewRange(n.lo, n.hi, n.not))
case *nodeAny:
result = append(result, match.NewAny(s))
case *nodeSuper:
result = append(result, match.NewSuper())
case *nodeSingle:
result = append(result, match.NewSingle(s))
case *nodeText:
result = append(result, match.NewText(n.text))
default:
return nil, fmt.Errorf("could not compile tree: unknown node type")
}
for i, m := range result {
result[i] = optimize(m)
}
return result, nil
}
func compile(ast *nodePattern, s []rune) (Glob, error) { func compile(ast *nodePattern, s []rune) (Glob, error) {
// ms, err := do2(ast, s)
// if err != nil {
// return nil, err
// }
// if len(ms) == 1 {
// return ms[0], nil
// } else {
// return match.NewAnyOf(ms), nil
// }
g, err := do(ast, s) g, err := do(ast, s)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -2,6 +2,7 @@ package glob
import ( import (
"github.com/gobwas/glob/match" "github.com/gobwas/glob/match"
"github.com/gobwas/glob/match/debug"
"reflect" "reflect"
"testing" "testing"
) )
@ -270,6 +271,19 @@ func TestCompiler(t *testing.T) {
nil, nil,
), ),
}, },
{
ast: pattern(&nodeText{text: "/"}, anyOf(&nodeText{text: "z"}, &nodeText{text: "ab"}), &nodeSuper{}),
sep: separators,
result: match.NewBTree(
match.NewText("/"),
nil,
match.NewBTree(
match.NewAnyOf(match.NewText("z"), match.NewText("ab")),
nil,
match.NewSuper(),
),
),
},
{ {
ast: pattern(&nodeSuper{}, &nodeSingle{}, &nodeText{text: "abc"}, &nodeSingle{}), ast: pattern(&nodeSuper{}, &nodeSingle{}, &nodeText{text: "abc"}, &nodeSingle{}),
sep: separators, sep: separators,
@ -397,18 +411,6 @@ func TestCompiler(t *testing.T) {
}..., }...,
), ),
}, },
// {
// ast: pattern(
// anyOf(&nodeText{text: "a"}, &nodeText{text: "b"}),
// anyOf(&nodeText{text: "c"}, &nodeText{text: "d"}),
// ),
// result: match.AnyOf{Matchers: match.Matchers{
// match.NewRow(Matchers: match.Matchers{match.Raw{"a"}, match.Raw{"c", 1}}),
// match.NewRow(Matchers: match.Matchers{match.Raw{"a"}, match.Raw{"d"}}),
// match.NewRow(Matchers: match.Matchers{match.Raw{"b"}, match.Raw{"c", 1}}),
// match.NewRow(Matchers: match.Matchers{match.Raw{"b"}, match.Raw{"d"}}),
// }},
// },
} { } {
m, err := compile(test.ast, test.sep) m, err := compile(test.ast, test.sep)
if err != nil { if err != nil {
@ -417,7 +419,7 @@ func TestCompiler(t *testing.T) {
} }
if !reflect.DeepEqual(m, test.result) { if !reflect.DeepEqual(m, test.result) {
t.Errorf("#%d results are not equal:\nexp: %#v\nact: %#v", id, test.result, m) t.Errorf("#%d results are not equal:\nexp: %#v\nact: %#v\nexp:\n%s\nact:\n%s\n", id, test.result, m, debug.Graphviz("", test.result.(match.Matcher)), debug.Graphviz("", m.(match.Matcher)))
continue continue
} }
} }

View File

@ -108,6 +108,10 @@ func TestGlob(t *testing.T) {
glob(true, "{*,**}{a,b}", "ab"), glob(true, "{*,**}{a,b}", "ab"),
glob(false, "{*,**}{a,b}", "ac"), glob(false, "{*,**}{a,b}", "ac"),
glob(true, "/{rate,[a-z][a-z][a-z]}*", "/rate"),
glob(true, "/{rate,[0-9][0-9][0-9]}*", "/rate"),
glob(true, "/{rate,[a-z][a-z][a-z]}*", "/usd"),
glob(true, pattern_all, fixture_all_match), glob(true, pattern_all, fixture_all_match),
glob(false, pattern_all, fixture_all_mismatch), glob(false, pattern_all, fixture_all_mismatch),
@ -162,7 +166,11 @@ func TestQuoteMeta(t *testing.T) {
}{ }{
{ {
in: `[foo*]`, in: `[foo*]`,
out: `\[foo\*\]`, out: `\[foo\*]`,
},
{
in: `{foo*}`,
out: `\{foo\*\}`,
}, },
{ {
in: string(specials), in: string(specials),

View File

@ -25,7 +25,6 @@ var specials = []byte{
char_single, char_single,
char_escape, char_escape,
char_range_open, char_range_open,
char_range_close,
char_terms_open, char_terms_open,
char_terms_close, char_terms_close,
} }
@ -123,6 +122,20 @@ func (i item) String() string {
return fmt.Sprintf("%v<%s>", i.t, i.s) return fmt.Sprintf("%v<%s>", i.t, i.s)
} }
type stubLexer struct {
Items []item
pos int
}
func (s *stubLexer) nextItem() (ret item) {
if s.pos == len(s.Items) {
return item{item_eof, ""}
}
ret = s.Items[s.pos]
s.pos++
return
}
type lexer struct { type lexer struct {
input string input string
start int start int
@ -269,21 +282,21 @@ func lexRaw(l *lexer) stateFn {
return lexTermsOpen return lexTermsOpen
case char_terms_close: case char_terms_close:
if l.inTerms() { // if we are in terms
l.unread() l.unread()
return lexTermsClose return lexTermsClose
}
case char_comma: case char_comma:
if l.inTerms() { // if we are not in terms if l.inTerms() { // if we are in terms
l.unread() l.unread()
return lexSeparator return lexSeparator
} }
fallthrough }
default:
l.unread() l.unread()
return lexText return lexText
} }
}
if l.pos > l.start { if l.pos > l.start {
l.emitCurrent(item_text) l.emitCurrent(item_text)
@ -325,7 +338,10 @@ scan:
escaped = false escaped = false
} }
if len(data) > 0 {
l.emit(item_text, string(data)) l.emit(item_text, string(data))
}
return lexRaw return lexRaw
} }

View File

@ -16,6 +16,24 @@ func TestLexGood(t *testing.T) {
item{item_eof, ""}, item{item_eof, ""},
}, },
}, },
{
pattern: "/{rate,[0-9]]}*",
items: []item{
item{item_text, "/"},
item{item_terms_open, "{"},
item{item_text, "rate"},
item{item_separator, ","},
item{item_range_open, "["},
item{item_range_lo, "0"},
item{item_range_between, "-"},
item{item_range_hi, "9"},
item{item_range_close, "]"},
item{item_text, "]"},
item{item_terms_close, "}"},
item{item_any, "*"},
item{item_eof, ""},
},
},
{ {
pattern: "hello,world", pattern: "hello,world",
items: []item{ items: []item{
@ -114,6 +132,19 @@ func TestLexGood(t *testing.T) {
item{item_eof, ""}, item{item_eof, ""},
}, },
}, },
{
pattern: "/{z,ab}*",
items: []item{
item{item_text, "/"},
item{item_terms_open, "{"},
item{item_text, "z"},
item{item_separator, ","},
item{item_text, "ab"},
item{item_terms_close, "}"},
item{item_any, "*"},
item{item_eof, ""},
},
},
{ {
pattern: "{[!日-語],*,?,{a,b,\\c}}", pattern: "{[!日-語],*,?,{a,b,\\c}}",
items: []item{ items: []item{

View File

@ -63,16 +63,15 @@ func (self AnyOf) Len() (l int) {
l = -1 l = -1
for _, m := range self.Matchers { for _, m := range self.Matchers {
ml := m.Len() ml := m.Len()
if ml == -1 { switch {
return -1 case l == -1:
}
if l == -1 {
l = ml l = ml
continue continue
}
if l != ml { case ml == -1:
return -1
case l != ml:
return -1 return -1
} }
} }

View File

@ -11,6 +11,11 @@ type node interface {
append(node) append(node)
} }
// todo may be split it into another package
type lexerIface interface {
nextItem() item
}
type nodeImpl struct { type nodeImpl struct {
desc []node desc []node
} }
@ -72,9 +77,9 @@ func (t *tree) leave() {
t.current = t.path[len(t.path)-1] t.current = t.path[len(t.path)-1]
} }
type parseFn func(*tree, *lexer) (parseFn, error) type parseFn func(*tree, lexerIface) (parseFn, error)
func parse(lexer *lexer) (*nodePattern, error) { func parse(lexer lexerIface) (*nodePattern, error) {
var parser parseFn var parser parseFn
root := &nodePattern{} root := &nodePattern{}
@ -97,7 +102,7 @@ func parse(lexer *lexer) (*nodePattern, error) {
return root, nil return root, nil
} }
func parserMain(tree *tree, lexer *lexer) (parseFn, error) { func parserMain(tree *tree, lexer lexerIface) (parseFn, error) {
for stop := false; !stop; { for stop := false; !stop; {
item := lexer.nextItem() item := lexer.nextItem()
@ -151,7 +156,7 @@ func parserMain(tree *tree, lexer *lexer) (parseFn, error) {
return nil, nil return nil, nil
} }
func parserRange(tree *tree, lexer *lexer) (parseFn, error) { func parserRange(tree *tree, lexer lexerIface) (parseFn, error) {
var ( var (
not bool not bool
lo rune lo rune

View File

@ -8,11 +8,15 @@ import (
func TestParseString(t *testing.T) { func TestParseString(t *testing.T) {
for id, test := range []struct { for id, test := range []struct {
pattern string items []item
tree node tree node
}{ }{
{ {
pattern: "abc", //pattern: "abc",
items: []item{
item{item_text, "abc"},
item{item_eof, ""},
},
tree: &nodePattern{ tree: &nodePattern{
nodeImpl: nodeImpl{ nodeImpl: nodeImpl{
desc: []node{ desc: []node{
@ -22,7 +26,13 @@ func TestParseString(t *testing.T) {
}, },
}, },
{ {
pattern: "a*c", //pattern: "a*c",
items: []item{
item{item_text, "a"},
item{item_any, "*"},
item{item_text, "c"},
item{item_eof, ""},
},
tree: &nodePattern{ tree: &nodePattern{
nodeImpl: nodeImpl{ nodeImpl: nodeImpl{
desc: []node{ desc: []node{
@ -34,7 +44,13 @@ func TestParseString(t *testing.T) {
}, },
}, },
{ {
pattern: "a**c", //pattern: "a**c",
items: []item{
item{item_text, "a"},
item{item_super, "**"},
item{item_text, "c"},
item{item_eof, ""},
},
tree: &nodePattern{ tree: &nodePattern{
nodeImpl: nodeImpl{ nodeImpl: nodeImpl{
desc: []node{ desc: []node{
@ -46,7 +62,13 @@ func TestParseString(t *testing.T) {
}, },
}, },
{ {
pattern: "a?c", //pattern: "a?c",
items: []item{
item{item_text, "a"},
item{item_single, "?"},
item{item_text, "c"},
item{item_eof, ""},
},
tree: &nodePattern{ tree: &nodePattern{
nodeImpl: nodeImpl{ nodeImpl: nodeImpl{
desc: []node{ desc: []node{
@ -58,7 +80,16 @@ func TestParseString(t *testing.T) {
}, },
}, },
{ {
pattern: "[!a-z]", //pattern: "[!a-z]",
items: []item{
item{item_range_open, "["},
item{item_not, "!"},
item{item_range_lo, "a"},
item{item_range_between, "-"},
item{item_range_hi, "z"},
item{item_range_close, "]"},
item{item_eof, ""},
},
tree: &nodePattern{ tree: &nodePattern{
nodeImpl: nodeImpl{ nodeImpl: nodeImpl{
desc: []node{ desc: []node{
@ -68,7 +99,13 @@ func TestParseString(t *testing.T) {
}, },
}, },
{ {
pattern: "[az]", //pattern: "[az]",
items: []item{
item{item_range_open, "["},
item{item_text, "az"},
item{item_range_close, "]"},
item{item_eof, ""},
},
tree: &nodePattern{ tree: &nodePattern{
nodeImpl: nodeImpl{ nodeImpl: nodeImpl{
desc: []node{ desc: []node{
@ -78,7 +115,15 @@ func TestParseString(t *testing.T) {
}, },
}, },
{ {
pattern: "{a,z}", //pattern: "{a,z}",
items: []item{
item{item_terms_open, "{"},
item{item_text, "a"},
item{item_separator, ","},
item{item_text, "z"},
item{item_terms_close, "}"},
item{item_eof, ""},
},
tree: &nodePattern{ tree: &nodePattern{
nodeImpl: nodeImpl{ nodeImpl: nodeImpl{
desc: []node{ desc: []node{
@ -99,7 +144,65 @@ func TestParseString(t *testing.T) {
}, },
}, },
{ {
pattern: "{a,{x,y},?,[a-z],[!qwe]}", //pattern: "/{z,ab}*",
items: []item{
item{item_text, "/"},
item{item_terms_open, "{"},
item{item_text, "z"},
item{item_separator, ","},
item{item_text, "ab"},
item{item_terms_close, "}"},
item{item_any, "*"},
item{item_eof, ""},
},
tree: &nodePattern{
nodeImpl: nodeImpl{
desc: []node{
&nodeText{text: "/"},
&nodeAnyOf{nodeImpl: nodeImpl{desc: []node{
&nodePattern{
nodeImpl: nodeImpl{desc: []node{
&nodeText{text: "z"},
}},
},
&nodePattern{
nodeImpl: nodeImpl{desc: []node{
&nodeText{text: "ab"},
}},
},
}}},
&nodeAny{},
},
},
},
},
{
//pattern: "{a,{x,y},?,[a-z],[!qwe]}",
items: []item{
item{item_terms_open, "{"},
item{item_text, "a"},
item{item_separator, ","},
item{item_terms_open, "{"},
item{item_text, "x"},
item{item_separator, ","},
item{item_text, "y"},
item{item_terms_close, "}"},
item{item_separator, ","},
item{item_single, "?"},
item{item_separator, ","},
item{item_range_open, "["},
item{item_range_lo, "a"},
item{item_range_between, "-"},
item{item_range_hi, "z"},
item{item_range_close, "]"},
item{item_separator, ","},
item{item_range_open, "["},
item{item_not, "!"},
item{item_text, "qwe"},
item{item_range_close, "]"},
item{item_terms_close, "}"},
item{item_eof, ""},
},
tree: &nodePattern{ tree: &nodePattern{
nodeImpl: nodeImpl{ nodeImpl: nodeImpl{
desc: []node{ desc: []node{
@ -150,7 +253,9 @@ func TestParseString(t *testing.T) {
}, },
}, },
} { } {
pattern, err := parse(newLexer(test.pattern)) lexer := &stubLexer{Items: test.items}
pattern, err := parse(lexer)
if err != nil { if err != nil {
t.Errorf("#%d %s", id, err) t.Errorf("#%d %s", id, err)
continue continue