use regex implementing wildcard

This commit is contained in:
finley
2022-07-08 23:48:42 +08:00
parent 9e155a617c
commit 98f986a363
3 changed files with 104 additions and 121 deletions

View File

@@ -280,7 +280,10 @@ func execPersist(db *DB, args [][]byte) redis.Reply {
// execKeys returns all keys matching the given pattern // execKeys returns all keys matching the given pattern
func execKeys(db *DB, args [][]byte) redis.Reply { 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) result := make([][]byte, 0)
db.data.ForEach(func(key string, val interface{}) bool { db.data.ForEach(func(key string, val interface{}) bool {
if pattern.IsMatch(key) { if pattern.IsMatch(key) {

View File

@@ -1,131 +1,75 @@
package wildcard package wildcard
const ( import (
normal = iota "errors"
all // * "regexp"
any // ? "strings"
setSymbol // []
rangSymbol // [a-b]
negSymbol // [^a]
) )
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 // Pattern represents a wildcard pattern
type Pattern struct { 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 // CompilePattern convert wildcard string to Pattern
func CompilePattern(src string) *Pattern { func CompilePattern(src string) (*Pattern, error) {
items := make([]*item, 0) regexSrc := strings.Builder{}
escape := false regexSrc.WriteByte('^')
inSet := false for i := 0; i < len(src); i++ {
var set map[byte]bool ch := src[i]
for _, v := range src { if ch == '\\' {
c := byte(v) if i == len(src)-1 {
if escape { return nil, errors.New("end with 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
} }
} else if c == ']' { regexSrc.WriteByte(ch)
if inSet { regexSrc.WriteByte(src[i+1])
inSet = false i++ // skip escaped character
typeCode := setSymbol } else if ch == '^' {
if _, ok := set['-']; ok { if i == 0 {
typeCode = rangSymbol regexSrc.WriteString(`\^`)
delete(set, '-') } 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 { } 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 { } else {
if inSet { regexSrc.WriteByte(ch)
set[c] = true
} else {
items = append(items, &item{typeCode: normal, character: c})
}
} }
} }
return &Pattern{ regexSrc.WriteByte('$')
items: items, 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 // IsMatch returns whether the given string matches pattern
func (p *Pattern) IsMatch(s string) bool { func (p *Pattern) IsMatch(s string) bool {
if len(p.items) == 0 { return p.exp.Match([]byte(s))
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]
} }

View File

@@ -3,11 +3,19 @@ package wildcard
import "testing" import "testing"
func TestWildCard(t *testing.T) { func TestWildCard(t *testing.T) {
p := CompilePattern("") p, err := CompilePattern("")
if err != nil {
t.Error(err)
return
}
if !p.IsMatch("") { if !p.IsMatch("") {
t.Error("expect true actually false") t.Error("expect true actually false")
} }
p = CompilePattern("a") p, err = CompilePattern("a")
if err != nil {
t.Error(err)
return
}
if !p.IsMatch("a") { if !p.IsMatch("a") {
t.Error("expect true actually false") t.Error("expect true actually false")
} }
@@ -16,7 +24,11 @@ func TestWildCard(t *testing.T) {
} }
// test '?' // test '?'
p = CompilePattern("a?") p, err = CompilePattern("a?")
if err != nil {
t.Error(err)
return
}
if !p.IsMatch("ab") { if !p.IsMatch("ab") {
t.Error("expect true actually false") t.Error("expect true actually false")
} }
@@ -31,7 +43,11 @@ func TestWildCard(t *testing.T) {
} }
// test * // test *
p = CompilePattern("a*") p, err = CompilePattern("a*")
if err != nil {
t.Error(err)
return
}
if !p.IsMatch("ab") { if !p.IsMatch("ab") {
t.Error("expect true actually false") t.Error("expect true actually false")
} }
@@ -46,7 +62,11 @@ func TestWildCard(t *testing.T) {
} }
// test [] // test []
p = CompilePattern("a[ab[]") p, err = CompilePattern("a[ab[]")
if err != nil {
t.Error(err)
return
}
if !p.IsMatch("ab") { if !p.IsMatch("ab") {
t.Error("expect true actually false") t.Error("expect true actually false")
} }
@@ -64,7 +84,11 @@ func TestWildCard(t *testing.T) {
} }
// test [a-c] // 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") { if !p.IsMatch("hallo") {
t.Error("expect true actually false") t.Error("expect true actually false")
} }
@@ -81,8 +105,12 @@ func TestWildCard(t *testing.T) {
t.Error("expect false actually true") t.Error("expect false actually true")
} }
// test [^] //test [^]
p = CompilePattern("h[^ab]llo") p, err = CompilePattern("h[^ab]llo")
if err != nil {
t.Error(err)
return
}
if p.IsMatch("hallo") { if p.IsMatch("hallo") {
t.Error("expect false actually true") t.Error("expect false actually true")
} }
@@ -94,12 +122,20 @@ func TestWildCard(t *testing.T) {
} }
// test escape // test escape
p = CompilePattern("\\\\") // pattern: \\ p, err = CompilePattern(`\\\\`)
if !p.IsMatch("\\") { if err != nil {
t.Error(err)
return
}
if !p.IsMatch(`\\`) {
t.Error("expect true actually false") t.Error("expect true actually false")
} }
p = CompilePattern("\\*") p, err = CompilePattern("\\*")
if err != nil {
t.Error(err)
return
}
if !p.IsMatch("*") { if !p.IsMatch("*") {
t.Error("expect true actually false") t.Error("expect true actually false")
} }