From 98f986a36369a96b378ee1ffa9511fc032e759e5 Mon Sep 17 00:00:00 2001 From: finley Date: Fri, 8 Jul 2022 23:48:42 +0800 Subject: [PATCH] use regex implementing wildcard --- database/keys.go | 5 +- lib/wildcard/wildcard.go | 162 +++++++++++----------------------- lib/wildcard/wildcard_test.go | 58 +++++++++--- 3 files changed, 104 insertions(+), 121 deletions(-) diff --git a/database/keys.go b/database/keys.go index f36452c..c9095d6 100644 --- a/database/keys.go +++ b/database/keys.go @@ -280,7 +280,10 @@ func execPersist(db *DB, args [][]byte) redis.Reply { // execKeys returns all keys matching the given pattern func execKeys(db *DB, args [][]byte) redis.Reply { - pattern := wildcard.CompilePattern(string(args[0])) + pattern, err := wildcard.CompilePattern(string(args[0])) + if err != nil { + return protocol.MakeErrReply("ERR illegal wildcard") + } result := make([][]byte, 0) db.data.ForEach(func(key string, val interface{}) bool { if pattern.IsMatch(key) { diff --git a/lib/wildcard/wildcard.go b/lib/wildcard/wildcard.go index 3a85ab6..a00c0b4 100644 --- a/lib/wildcard/wildcard.go +++ b/lib/wildcard/wildcard.go @@ -1,131 +1,75 @@ package wildcard -const ( - normal = iota - all // * - any // ? - setSymbol // [] - rangSymbol // [a-b] - negSymbol // [^a] +import ( + "errors" + "regexp" + "strings" ) -type item struct { - character byte - set map[byte]bool - typeCode int -} - -func (i *item) contains(c byte) bool { - if i.typeCode == setSymbol { - _, ok := i.set[c] - return ok - } else if i.typeCode == rangSymbol { - if _, ok := i.set[c]; ok { - return true - } - var ( - min uint8 = 255 - max uint8 = 0 - ) - for k := range i.set { - if min > k { - min = k - } - if max < k { - max = k - } - } - return c >= min && c <= max - } else { - _, ok := i.set[c] - return !ok - } -} - // Pattern represents a wildcard pattern type Pattern struct { - items []*item + exp *regexp.Regexp +} + +var replaceMap = map[byte]string{ + // characters in the wildcard that must be escaped in the regexp + '+': `\+`, + ')': `\)`, + '$': `\$`, + '.': `\.`, + '{': `\{`, + '}': `\}`, + '|': `\|`, + '*': ".*", + '?': ".", } // CompilePattern convert wildcard string to Pattern -func CompilePattern(src string) *Pattern { - items := make([]*item, 0) - escape := false - inSet := false - var set map[byte]bool - for _, v := range src { - c := byte(v) - if escape { - items = append(items, &item{typeCode: normal, character: c}) - escape = false - } else if c == '*' { - items = append(items, &item{typeCode: all}) - } else if c == '?' { - items = append(items, &item{typeCode: any}) - } else if c == '\\' { - escape = true - } else if c == '[' { - if !inSet { - inSet = true - set = make(map[byte]bool) - } else { - set[c] = true +func CompilePattern(src string) (*Pattern, error) { + regexSrc := strings.Builder{} + regexSrc.WriteByte('^') + for i := 0; i < len(src); i++ { + ch := src[i] + if ch == '\\' { + if i == len(src)-1 { + return nil, errors.New("end with escape \\") } - } else if c == ']' { - if inSet { - inSet = false - typeCode := setSymbol - if _, ok := set['-']; ok { - typeCode = rangSymbol - delete(set, '-') + regexSrc.WriteByte(ch) + regexSrc.WriteByte(src[i+1]) + i++ // skip escaped character + } else if ch == '^' { + if i == 0 { + regexSrc.WriteString(`\^`) + } else if i == 1 { + if src[i-1] == '[' { + regexSrc.WriteString(`^`) // src is: [^ + } else { + regexSrc.WriteString(`\^`) } - if _, ok := set['^']; ok { - typeCode = negSymbol - delete(set, '^') - } - items = append(items, &item{typeCode: typeCode, set: set}) } else { - items = append(items, &item{typeCode: normal, character: c}) + if src[i-1] == '[' && src[i-2] != '\\' { + regexSrc.WriteString(`^`) // src is: [^, except \[^ + } else { + regexSrc.WriteString(`\^`) + } } + } else if escaped, toEscape := replaceMap[ch]; toEscape { + regexSrc.WriteString(escaped) } else { - if inSet { - set[c] = true - } else { - items = append(items, &item{typeCode: normal, character: c}) - } + regexSrc.WriteByte(ch) } } - return &Pattern{ - items: items, + regexSrc.WriteByte('$') + re, err := regexp.Compile(regexSrc.String()) + if err != nil { + return nil, err } + return &Pattern{ + exp: re, + }, nil } // IsMatch returns whether the given string matches pattern func (p *Pattern) IsMatch(s string) bool { - if len(p.items) == 0 { - return len(s) == 0 - } - m := len(s) - n := len(p.items) - table := make([][]bool, m+1) - for i := 0; i < m+1; i++ { - table[i] = make([]bool, n+1) - } - table[0][0] = true - for j := 1; j < n+1; j++ { - table[0][j] = table[0][j-1] && p.items[j-1].typeCode == all - } - for i := 1; i < m+1; i++ { - for j := 1; j < n+1; j++ { - if p.items[j-1].typeCode == all { - table[i][j] = table[i-1][j] || table[i][j-1] - } else { - table[i][j] = table[i-1][j-1] && - (p.items[j-1].typeCode == any || - (p.items[j-1].typeCode == normal && uint8(s[i-1]) == p.items[j-1].character) || - (p.items[j-1].typeCode >= setSymbol && p.items[j-1].contains(s[i-1]))) - } - } - } - return table[m][n] + return p.exp.Match([]byte(s)) } diff --git a/lib/wildcard/wildcard_test.go b/lib/wildcard/wildcard_test.go index f2f3d9a..614d043 100644 --- a/lib/wildcard/wildcard_test.go +++ b/lib/wildcard/wildcard_test.go @@ -3,11 +3,19 @@ package wildcard import "testing" func TestWildCard(t *testing.T) { - p := CompilePattern("") + p, err := CompilePattern("") + if err != nil { + t.Error(err) + return + } if !p.IsMatch("") { t.Error("expect true actually false") } - p = CompilePattern("a") + p, err = CompilePattern("a") + if err != nil { + t.Error(err) + return + } if !p.IsMatch("a") { t.Error("expect true actually false") } @@ -16,7 +24,11 @@ func TestWildCard(t *testing.T) { } // test '?' - p = CompilePattern("a?") + p, err = CompilePattern("a?") + if err != nil { + t.Error(err) + return + } if !p.IsMatch("ab") { t.Error("expect true actually false") } @@ -31,7 +43,11 @@ func TestWildCard(t *testing.T) { } // test * - p = CompilePattern("a*") + p, err = CompilePattern("a*") + if err != nil { + t.Error(err) + return + } if !p.IsMatch("ab") { t.Error("expect true actually false") } @@ -46,7 +62,11 @@ func TestWildCard(t *testing.T) { } // test [] - p = CompilePattern("a[ab[]") + p, err = CompilePattern("a[ab[]") + if err != nil { + t.Error(err) + return + } if !p.IsMatch("ab") { t.Error("expect true actually false") } @@ -64,7 +84,11 @@ func TestWildCard(t *testing.T) { } // test [a-c] - p = CompilePattern("h[a-c]llo") + p, err = CompilePattern("h[a-c]llo") + if err != nil { + t.Error(err) + return + } if !p.IsMatch("hallo") { t.Error("expect true actually false") } @@ -81,8 +105,12 @@ func TestWildCard(t *testing.T) { t.Error("expect false actually true") } - // test [^] - p = CompilePattern("h[^ab]llo") + //test [^] + p, err = CompilePattern("h[^ab]llo") + if err != nil { + t.Error(err) + return + } if p.IsMatch("hallo") { t.Error("expect false actually true") } @@ -94,12 +122,20 @@ func TestWildCard(t *testing.T) { } // test escape - p = CompilePattern("\\\\") // pattern: \\ - if !p.IsMatch("\\") { + p, err = CompilePattern(`\\\\`) + if err != nil { + t.Error(err) + return + } + if !p.IsMatch(`\\`) { t.Error("expect true actually false") } - p = CompilePattern("\\*") + p, err = CompilePattern("\\*") + if err != nil { + t.Error(err) + return + } if !p.IsMatch("*") { t.Error("expect true actually false") }