Because it's literally ten times faster: benchmark old ns/op new ns/op delta BenchmarkMatch-8 13842 1200 -91.33% BenchmarkMatchCached-8 139 147 +5.76% benchmark old allocs new allocs delta BenchmarkMatch-8 0 0 +0.00% BenchmarkMatchCached-8 0 0 +0.00% benchmark old bytes new bytes delta BenchmarkMatch-8 12 0 -100.00% BenchmarkMatchCached-8 0 0 +0.00%
226 lines
3.6 KiB
Go
226 lines
3.6 KiB
Go
package glob
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
type node interface {
|
|
children() []node
|
|
append(node)
|
|
}
|
|
|
|
type nodeImpl struct {
|
|
desc []node
|
|
}
|
|
|
|
func (n *nodeImpl) append(c node) {
|
|
n.desc = append(n.desc, c)
|
|
}
|
|
func (n *nodeImpl) children() []node {
|
|
return n.desc
|
|
}
|
|
|
|
type nodeList struct {
|
|
nodeImpl
|
|
not bool
|
|
chars string
|
|
}
|
|
type nodeRange struct {
|
|
nodeImpl
|
|
not bool
|
|
lo, hi rune
|
|
}
|
|
type nodeText struct {
|
|
nodeImpl
|
|
text string
|
|
}
|
|
|
|
type nodePattern struct{ nodeImpl }
|
|
type nodeAny struct{ nodeImpl }
|
|
type nodeSuper struct{ nodeImpl }
|
|
type nodeSingle struct{ nodeImpl }
|
|
type nodeAnyOf struct{ nodeImpl }
|
|
|
|
type tree struct {
|
|
root node
|
|
current node
|
|
path []node
|
|
}
|
|
|
|
func (t *tree) enter(c node) {
|
|
if t.root == nil {
|
|
t.root = c
|
|
t.current = c
|
|
return
|
|
}
|
|
|
|
t.current.append(c)
|
|
t.path = append(t.path, c)
|
|
t.current = c
|
|
}
|
|
|
|
func (t *tree) leave() {
|
|
if len(t.path)-1 <= 0 {
|
|
t.current = t.root
|
|
t.path = nil
|
|
return
|
|
}
|
|
|
|
t.path = t.path[:len(t.path)-1]
|
|
t.current = t.path[len(t.path)-1]
|
|
}
|
|
|
|
type parseFn func(*tree, *lexer) (parseFn, error)
|
|
|
|
func parse(lexer *lexer) (*nodePattern, error) {
|
|
var parser parseFn
|
|
|
|
root := &nodePattern{}
|
|
tree := &tree{}
|
|
tree.enter(root)
|
|
|
|
for parser = parserMain; ; {
|
|
next, err := parser(tree, lexer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if next == nil {
|
|
break
|
|
}
|
|
|
|
parser = next
|
|
}
|
|
|
|
return root, nil
|
|
}
|
|
|
|
func parserMain(tree *tree, lexer *lexer) (parseFn, error) {
|
|
for stop := false; !stop; {
|
|
item := lexer.nextItem()
|
|
|
|
switch item.t {
|
|
case item_eof:
|
|
stop = true
|
|
continue
|
|
|
|
case item_error:
|
|
return nil, errors.New(item.s)
|
|
|
|
case item_text:
|
|
tree.current.append(&nodeText{text: item.s})
|
|
return parserMain, nil
|
|
|
|
case item_any:
|
|
tree.current.append(&nodeAny{})
|
|
return parserMain, nil
|
|
|
|
case item_super:
|
|
tree.current.append(&nodeSuper{})
|
|
return parserMain, nil
|
|
|
|
case item_single:
|
|
tree.current.append(&nodeSingle{})
|
|
return parserMain, nil
|
|
|
|
case item_range_open:
|
|
return parserRange, nil
|
|
|
|
case item_terms_open:
|
|
tree.enter(&nodeAnyOf{})
|
|
tree.enter(&nodePattern{})
|
|
return parserMain, nil
|
|
|
|
case item_separator:
|
|
tree.leave()
|
|
tree.enter(&nodePattern{})
|
|
return parserMain, nil
|
|
|
|
case item_terms_close:
|
|
tree.leave()
|
|
tree.leave()
|
|
return parserMain, nil
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unexpected token: %s", item)
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func parserRange(tree *tree, lexer *lexer) (parseFn, error) {
|
|
var (
|
|
not bool
|
|
lo rune
|
|
hi rune
|
|
chars string
|
|
)
|
|
|
|
for {
|
|
item := lexer.nextItem()
|
|
|
|
switch item.t {
|
|
case item_eof:
|
|
return nil, errors.New("unexpected end")
|
|
|
|
case item_error:
|
|
return nil, errors.New(item.s)
|
|
|
|
case item_not:
|
|
not = true
|
|
|
|
case item_range_lo:
|
|
r, w := utf8.DecodeRuneInString(item.s)
|
|
if len(item.s) > w {
|
|
return nil, fmt.Errorf("unexpected length of lo character")
|
|
}
|
|
|
|
lo = r
|
|
|
|
case item_range_between:
|
|
//
|
|
|
|
case item_range_hi:
|
|
r, w := utf8.DecodeRuneInString(item.s)
|
|
if len(item.s) > w {
|
|
return nil, fmt.Errorf("unexpected length of lo character")
|
|
}
|
|
|
|
hi = r
|
|
|
|
if hi < lo {
|
|
return nil, fmt.Errorf("hi character '%s' should be greater than lo '%s'", string(hi), string(lo))
|
|
}
|
|
|
|
case item_text:
|
|
chars = item.s
|
|
|
|
case item_range_close:
|
|
isRange := lo != 0 && hi != 0
|
|
isChars := chars != ""
|
|
|
|
if isChars == isRange {
|
|
return nil, fmt.Errorf("could not parse range")
|
|
}
|
|
|
|
if isRange {
|
|
tree.current.append(&nodeRange{
|
|
lo: lo,
|
|
hi: hi,
|
|
not: not,
|
|
})
|
|
} else {
|
|
tree.current.append(&nodeList{
|
|
chars: chars,
|
|
not: not,
|
|
})
|
|
}
|
|
|
|
return parserMain, nil
|
|
}
|
|
}
|
|
}
|