mirror of
https://github.com/golang/go
synced 2024-11-23 20:50:04 -07:00
regexp: split one-pass execution out of machine struct
This allows the one-pass executions to have their own pool of (much smaller) allocated structures. A step toward eliminating the per-Regexp machine cache. Not much effect on benchmarks, since there are no optimizations here, and pools are a tiny bit slower than a locked data structure for single-threaded code. name old time/op new time/op delta Find-12 254ns ± 0% 252ns ± 0% -0.94% (p=0.000 n=9+10) FindAllNoMatches-12 135ns ± 0% 134ns ± 1% -0.49% (p=0.002 n=9+9) FindString-12 247ns ± 0% 246ns ± 0% -0.24% (p=0.003 n=8+10) FindSubmatch-12 334ns ± 0% 333ns ± 2% ~ (p=0.283 n=10+10) FindStringSubmatch-12 321ns ± 0% 320ns ± 0% -0.51% (p=0.000 n=9+10) Literal-12 92.2ns ± 0% 91.1ns ± 0% -1.25% (p=0.000 n=9+10) NotLiteral-12 1.47µs ± 0% 1.45µs ± 0% -0.99% (p=0.000 n=9+10) MatchClass-12 2.17µs ± 0% 2.19µs ± 0% +0.84% (p=0.000 n=7+9) MatchClass_InRange-12 2.13µs ± 0% 2.09µs ± 0% -1.70% (p=0.000 n=10+10) ReplaceAll-12 1.39µs ± 0% 1.39µs ± 0% +0.51% (p=0.000 n=10+10) AnchoredLiteralShortNonMatch-12 83.2ns ± 0% 82.4ns ± 0% -0.96% (p=0.000 n=8+8) AnchoredLiteralLongNonMatch-12 105ns ± 0% 106ns ± 1% ~ (p=0.087 n=10+10) AnchoredShortMatch-12 131ns ± 0% 130ns ± 0% -0.76% (p=0.000 n=10+9) AnchoredLongMatch-12 267ns ± 0% 272ns ± 0% +2.01% (p=0.000 n=10+8) OnePassShortA-12 611ns ± 0% 615ns ± 0% +0.61% (p=0.000 n=9+10) NotOnePassShortA-12 552ns ± 0% 549ns ± 0% -0.46% (p=0.000 n=8+9) OnePassShortB-12 491ns ± 0% 494ns ± 0% +0.61% (p=0.000 n=8+8) NotOnePassShortB-12 412ns ± 0% 412ns ± 1% ~ (p=0.151 n=9+10) OnePassLongPrefix-12 112ns ± 0% 108ns ± 0% -3.57% (p=0.000 n=10+10) OnePassLongNotPrefix-12 410ns ± 0% 402ns ± 0% -1.95% (p=0.000 n=9+8) MatchParallelShared-12 38.8ns ± 1% 38.6ns ± 2% ~ (p=0.536 n=10+9) MatchParallelCopied-12 39.2ns ± 3% 39.4ns ± 7% ~ (p=0.986 n=10+10) QuoteMetaAll-12 94.6ns ± 0% 94.9ns ± 0% +0.29% (p=0.001 n=8+9) QuoteMetaNone-12 52.7ns ± 0% 52.7ns ± 0% ~ (all equal) Match/Easy0/32-12 72.9ns ± 0% 72.1ns ± 0% -1.07% (p=0.000 n=9+9) Match/Easy0/1K-12 298ns ± 0% 298ns ± 0% ~ (p=0.140 n=6+8) Match/Easy0/32K-12 4.60µs ± 2% 4.64µs ± 1% ~ (p=0.171 n=10+10) Match/Easy0/1M-12 235µs ± 0% 234µs ± 0% -0.14% (p=0.004 n=10+10) Match/Easy0/32M-12 7.96ms ± 0% 7.95ms ± 0% -0.12% (p=0.043 n=10+9) Match/Easy0i/32-12 1.09µs ± 0% 1.10µs ± 0% +0.15% (p=0.000 n=8+9) Match/Easy0i/1K-12 31.7µs ± 0% 31.8µs ± 1% ~ (p=0.905 n=9+10) Match/Easy0i/32K-12 1.61ms ± 0% 1.62ms ± 1% +1.12% (p=0.000 n=9+10) Match/Easy0i/1M-12 51.4ms ± 0% 51.8ms ± 0% +0.85% (p=0.000 n=8+8) Match/Easy0i/32M-12 1.65s ± 1% 1.65s ± 0% ~ (p=0.113 n=9+9) Match/Easy1/32-12 67.9ns ± 0% 67.7ns ± 1% ~ (p=0.232 n=8+10) Match/Easy1/1K-12 884ns ± 0% 873ns ± 0% -1.29% (p=0.000 n=9+10) Match/Easy1/32K-12 39.2µs ± 0% 39.4µs ± 0% +0.50% (p=0.000 n=9+10) Match/Easy1/1M-12 1.39ms ± 0% 1.39ms ± 0% +0.29% (p=0.000 n=9+10) Match/Easy1/32M-12 44.2ms ± 1% 44.3ms ± 0% +0.21% (p=0.029 n=10+10) Match/Medium/32-12 1.05µs ± 0% 1.04µs ± 0% -0.27% (p=0.001 n=8+9) Match/Medium/1K-12 31.3µs ± 0% 31.4µs ± 0% +0.39% (p=0.000 n=9+8) Match/Medium/32K-12 1.45ms ± 0% 1.45ms ± 0% +0.33% (p=0.000 n=8+9) Match/Medium/1M-12 46.2ms ± 0% 46.4ms ± 0% +0.35% (p=0.000 n=9+8) Match/Medium/32M-12 1.48s ± 0% 1.49s ± 1% +0.70% (p=0.000 n=8+10) Match/Hard/32-12 1.49µs ± 0% 1.48µs ± 0% -0.43% (p=0.000 n=10+9) Match/Hard/1K-12 45.1µs ± 1% 45.0µs ± 1% ~ (p=0.393 n=10+10) Match/Hard/32K-12 2.18ms ± 1% 2.24ms ± 0% +2.71% (p=0.000 n=9+8) Match/Hard/1M-12 69.7ms ± 1% 71.6ms ± 0% +2.76% (p=0.000 n=9+7) Match/Hard/32M-12 2.23s ± 1% 2.29s ± 0% +2.65% (p=0.000 n=9+9) Match/Hard1/32-12 7.89µs ± 0% 7.89µs ± 0% ~ (p=0.286 n=9+9) Match/Hard1/1K-12 244µs ± 0% 244µs ± 0% ~ (p=0.905 n=9+10) Match/Hard1/32K-12 10.3ms ± 0% 10.3ms ± 0% ~ (p=0.796 n=10+10) Match/Hard1/1M-12 331ms ± 0% 331ms ± 0% ~ (p=0.167 n=8+9) Match/Hard1/32M-12 10.6s ± 0% 10.6s ± 0% ~ (p=0.315 n=8+10) Match_onepass_regex/32-12 812ns ± 0% 830ns ± 0% +2.19% (p=0.000 n=10+9) Match_onepass_regex/1K-12 28.5µs ± 0% 28.7µs ± 1% +0.97% (p=0.000 n=10+9) Match_onepass_regex/32K-12 936µs ± 0% 949µs ± 0% +1.43% (p=0.000 n=10+8) Match_onepass_regex/1M-12 30.2ms ± 0% 30.4ms ± 0% +0.62% (p=0.000 n=10+8) Match_onepass_regex/32M-12 970ms ± 0% 973ms ± 0% +0.35% (p=0.000 n=10+9) CompileOnepass-12 4.63µs ± 1% 4.64µs ± 0% ~ (p=0.060 n=10+10) [Geo mean] 23.3µs 23.3µs +0.12% https://perf.golang.org/search?q=upload:20181004.2 Change-Id: Iff9e9f9d4a4698162126a2f300e8ed1b1a39361e Reviewed-on: https://go-review.googlesource.com/c/139780 Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
2d4346b319
commit
60b2971180
@ -550,8 +550,8 @@ func TestOnePassCutoff(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("compile: %v", err)
|
||||
}
|
||||
if compileOnePass(p) != notOnePass {
|
||||
t.Fatalf("makeOnePass succeeded; wanted notOnePass")
|
||||
if compileOnePass(p) != nil {
|
||||
t.Fatalf("makeOnePass succeeded; wanted nil")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ package regexp
|
||||
import (
|
||||
"io"
|
||||
"regexp/syntax"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A queue is a 'sparse array' holding pending threads of execution.
|
||||
@ -37,7 +38,6 @@ type thread struct {
|
||||
type machine struct {
|
||||
re *Regexp // corresponding Regexp
|
||||
p *syntax.Prog // compiled program
|
||||
op *onePassProg // compiled onepass program, or notOnePass
|
||||
q0, q1 queue // two queues for runq, nextq
|
||||
pool []*thread // pool of available threads
|
||||
matched bool // whether a match was found
|
||||
@ -93,8 +93,8 @@ func (i *inputs) init(r io.RuneReader, b []byte, s string) (input, int) {
|
||||
}
|
||||
|
||||
// progMachine returns a new machine running the prog p.
|
||||
func progMachine(p *syntax.Prog, op *onePassProg) *machine {
|
||||
m := &machine{p: p, op: op}
|
||||
func progMachine(p *syntax.Prog) *machine {
|
||||
m := &machine{p: p}
|
||||
n := len(m.p.Inst)
|
||||
m.q0 = queue{make([]uint32, n), make([]entry, 0, n)}
|
||||
m.q1 = queue{make([]uint32, n), make([]entry, 0, n)}
|
||||
@ -327,20 +327,47 @@ func (m *machine) add(q *queue, pc uint32, pos int, cap []int, cond syntax.Empty
|
||||
return t
|
||||
}
|
||||
|
||||
// onepass runs the machine over the input starting at pos.
|
||||
// It reports whether a match was found.
|
||||
// If so, m.matchcap holds the submatch information.
|
||||
// ncap is the number of captures.
|
||||
func (m *machine) onepass(i input, pos, ncap int) bool {
|
||||
startCond := m.re.cond
|
||||
if startCond == ^syntax.EmptyOp(0) { // impossible
|
||||
return false
|
||||
type onePassMachine struct {
|
||||
inputs inputs
|
||||
matchcap []int
|
||||
}
|
||||
|
||||
var onePassPool sync.Pool
|
||||
|
||||
func newOnePassMachine() *onePassMachine {
|
||||
m, ok := onePassPool.Get().(*onePassMachine)
|
||||
if !ok {
|
||||
m = new(onePassMachine)
|
||||
}
|
||||
m.matched = false
|
||||
m.matchcap = m.matchcap[:ncap]
|
||||
return m
|
||||
}
|
||||
|
||||
func freeOnePassMachine(m *onePassMachine) {
|
||||
m.inputs.clear()
|
||||
onePassPool.Put(m)
|
||||
}
|
||||
|
||||
// doOnePass implements r.doExecute using the one-pass execution engine.
|
||||
func (re *Regexp) doOnePass(ir io.RuneReader, ib []byte, is string, pos, ncap int, dstCap []int) []int {
|
||||
startCond := re.cond
|
||||
if startCond == ^syntax.EmptyOp(0) { // impossible
|
||||
return nil
|
||||
}
|
||||
|
||||
m := newOnePassMachine()
|
||||
if cap(m.matchcap) < ncap {
|
||||
m.matchcap = make([]int, ncap)
|
||||
} else {
|
||||
m.matchcap = m.matchcap[:ncap]
|
||||
}
|
||||
|
||||
matched := false
|
||||
for i := range m.matchcap {
|
||||
m.matchcap[i] = -1
|
||||
}
|
||||
|
||||
i, _ := m.inputs.init(ir, ib, is)
|
||||
|
||||
r, r1 := endOfText, endOfText
|
||||
width, width1 := 0, 0
|
||||
r, width = i.step(pos)
|
||||
@ -353,59 +380,59 @@ func (m *machine) onepass(i input, pos, ncap int) bool {
|
||||
} else {
|
||||
flag = i.context(pos)
|
||||
}
|
||||
pc := m.op.Start
|
||||
inst := m.op.Inst[pc]
|
||||
pc := re.onepass.Start
|
||||
inst := re.onepass.Inst[pc]
|
||||
// If there is a simple literal prefix, skip over it.
|
||||
if pos == 0 && syntax.EmptyOp(inst.Arg)&^flag == 0 &&
|
||||
len(m.re.prefix) > 0 && i.canCheckPrefix() {
|
||||
len(re.prefix) > 0 && i.canCheckPrefix() {
|
||||
// Match requires literal prefix; fast search for it.
|
||||
if !i.hasPrefix(m.re) {
|
||||
return m.matched
|
||||
if !i.hasPrefix(re) {
|
||||
goto Return
|
||||
}
|
||||
pos += len(m.re.prefix)
|
||||
pos += len(re.prefix)
|
||||
r, width = i.step(pos)
|
||||
r1, width1 = i.step(pos + width)
|
||||
flag = i.context(pos)
|
||||
pc = int(m.re.prefixEnd)
|
||||
pc = int(re.prefixEnd)
|
||||
}
|
||||
for {
|
||||
inst = m.op.Inst[pc]
|
||||
inst = re.onepass.Inst[pc]
|
||||
pc = int(inst.Out)
|
||||
switch inst.Op {
|
||||
default:
|
||||
panic("bad inst")
|
||||
case syntax.InstMatch:
|
||||
m.matched = true
|
||||
matched = true
|
||||
if len(m.matchcap) > 0 {
|
||||
m.matchcap[0] = 0
|
||||
m.matchcap[1] = pos
|
||||
}
|
||||
return m.matched
|
||||
goto Return
|
||||
case syntax.InstRune:
|
||||
if !inst.MatchRune(r) {
|
||||
return m.matched
|
||||
goto Return
|
||||
}
|
||||
case syntax.InstRune1:
|
||||
if r != inst.Rune[0] {
|
||||
return m.matched
|
||||
goto Return
|
||||
}
|
||||
case syntax.InstRuneAny:
|
||||
// Nothing
|
||||
case syntax.InstRuneAnyNotNL:
|
||||
if r == '\n' {
|
||||
return m.matched
|
||||
goto Return
|
||||
}
|
||||
// peek at the input rune to see which branch of the Alt to take
|
||||
case syntax.InstAlt, syntax.InstAltMatch:
|
||||
pc = int(onePassNext(&inst, r))
|
||||
continue
|
||||
case syntax.InstFail:
|
||||
return m.matched
|
||||
goto Return
|
||||
case syntax.InstNop:
|
||||
continue
|
||||
case syntax.InstEmptyWidth:
|
||||
if syntax.EmptyOp(inst.Arg)&^flag != 0 {
|
||||
return m.matched
|
||||
goto Return
|
||||
}
|
||||
continue
|
||||
case syntax.InstCapture:
|
||||
@ -424,7 +451,16 @@ func (m *machine) onepass(i input, pos, ncap int) bool {
|
||||
r1, width1 = i.step(pos + width)
|
||||
}
|
||||
}
|
||||
return m.matched
|
||||
|
||||
Return:
|
||||
if !matched {
|
||||
freeOnePassMachine(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
dstCap = append(dstCap, m.matchcap...)
|
||||
freeOnePassMachine(m)
|
||||
return dstCap
|
||||
}
|
||||
|
||||
// doMatch reports whether either r, b or s match the regexp.
|
||||
@ -442,25 +478,22 @@ func (re *Regexp) doExecute(r io.RuneReader, b []byte, s string, pos int, ncap i
|
||||
dstCap = arrayNoInts[:0:0]
|
||||
}
|
||||
|
||||
if re.onepass == notOnePass && r == nil && len(b)+len(s) < re.maxBitStateLen {
|
||||
if re.onepass != nil {
|
||||
return re.doOnePass(r, b, s, pos, ncap, dstCap)
|
||||
}
|
||||
if r == nil && len(b)+len(s) < re.maxBitStateLen {
|
||||
return re.backtrack(b, s, pos, ncap, dstCap)
|
||||
}
|
||||
|
||||
m := re.get()
|
||||
i, _ := m.inputs.init(r, b, s)
|
||||
|
||||
if m.op != notOnePass {
|
||||
if !m.onepass(i, pos, ncap) {
|
||||
re.put(m)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
m.init(ncap)
|
||||
if !m.match(i, pos) {
|
||||
re.put(m)
|
||||
return nil
|
||||
}
|
||||
m.init(ncap)
|
||||
if !m.match(i, pos) {
|
||||
re.put(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
dstCap = append(dstCap, m.matchcap...)
|
||||
re.put(m)
|
||||
return dstCap
|
||||
|
@ -684,7 +684,7 @@ func BenchmarkMatch(b *testing.B) {
|
||||
func BenchmarkMatch_onepass_regex(b *testing.B) {
|
||||
isRaceBuilder := strings.HasSuffix(testenv.Builder(), "-race")
|
||||
r := MustCompile(`(?s)\A.*\z`)
|
||||
if r.get().op == notOnePass {
|
||||
if r.onepass == nil {
|
||||
b.Fatalf("want onepass regex, but %q is not onepass", r)
|
||||
}
|
||||
for _, size := range benchSizes {
|
||||
|
@ -294,12 +294,12 @@ var anyRune = []rune{0, unicode.MaxRune}
|
||||
// makeOnePass creates a onepass Prog, if possible. It is possible if at any alt,
|
||||
// the match engine can always tell which branch to take. The routine may modify
|
||||
// p if it is turned into a onepass Prog. If it isn't possible for this to be a
|
||||
// onepass Prog, the Prog notOnePass is returned. makeOnePass is recursive
|
||||
// onepass Prog, the Prog nil is returned. makeOnePass is recursive
|
||||
// to the size of the Prog.
|
||||
func makeOnePass(p *onePassProg) *onePassProg {
|
||||
// If the machine is very long, it's not worth the time to check if we can use one pass.
|
||||
if len(p.Inst) >= 1000 {
|
||||
return notOnePass
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
@ -446,11 +446,11 @@ func makeOnePass(p *onePassProg) *onePassProg {
|
||||
visitQueue.clear()
|
||||
pc := instQueue.next()
|
||||
if !check(pc, m) {
|
||||
p = notOnePass
|
||||
p = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
if p != notOnePass {
|
||||
if p != nil {
|
||||
for i := range p.Inst {
|
||||
p.Inst[i].Rune = onePassRunes[i]
|
||||
}
|
||||
@ -458,20 +458,18 @@ func makeOnePass(p *onePassProg) *onePassProg {
|
||||
return p
|
||||
}
|
||||
|
||||
var notOnePass *onePassProg = nil
|
||||
|
||||
// compileOnePass returns a new *syntax.Prog suitable for onePass execution if the original Prog
|
||||
// can be recharacterized as a one-pass regexp program, or syntax.notOnePass if the
|
||||
// can be recharacterized as a one-pass regexp program, or syntax.nil if the
|
||||
// Prog cannot be converted. For a one pass prog, the fundamental condition that must
|
||||
// be true is: at any InstAlt, there must be no ambiguity about what branch to take.
|
||||
func compileOnePass(prog *syntax.Prog) (p *onePassProg) {
|
||||
if prog.Start == 0 {
|
||||
return notOnePass
|
||||
return nil
|
||||
}
|
||||
// onepass regexp is anchored
|
||||
if prog.Inst[prog.Start].Op != syntax.InstEmptyWidth ||
|
||||
syntax.EmptyOp(prog.Inst[prog.Start].Arg)&syntax.EmptyBeginText != syntax.EmptyBeginText {
|
||||
return notOnePass
|
||||
return nil
|
||||
}
|
||||
// every instruction leading to InstMatch must be EmptyEndText
|
||||
for _, inst := range prog.Inst {
|
||||
@ -479,18 +477,18 @@ func compileOnePass(prog *syntax.Prog) (p *onePassProg) {
|
||||
switch inst.Op {
|
||||
default:
|
||||
if opOut == syntax.InstMatch {
|
||||
return notOnePass
|
||||
return nil
|
||||
}
|
||||
case syntax.InstAlt, syntax.InstAltMatch:
|
||||
if opOut == syntax.InstMatch || prog.Inst[inst.Arg].Op == syntax.InstMatch {
|
||||
return notOnePass
|
||||
return nil
|
||||
}
|
||||
case syntax.InstEmptyWidth:
|
||||
if opOut == syntax.InstMatch {
|
||||
if syntax.EmptyOp(inst.Arg)&syntax.EmptyEndText == syntax.EmptyEndText {
|
||||
continue
|
||||
}
|
||||
return notOnePass
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -501,7 +499,7 @@ func compileOnePass(prog *syntax.Prog) (p *onePassProg) {
|
||||
// checkAmbiguity on InstAlts, build onepass Prog if possible
|
||||
p = makeOnePass(p)
|
||||
|
||||
if p != notOnePass {
|
||||
if p != nil {
|
||||
cleanupOnePass(p, prog)
|
||||
}
|
||||
return p
|
||||
|
@ -134,47 +134,45 @@ func TestMergeRuneSet(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var onePass = &onePassProg{}
|
||||
|
||||
var onePassTests = []struct {
|
||||
re string
|
||||
onePass *onePassProg
|
||||
re string
|
||||
isOnePass bool
|
||||
}{
|
||||
{`^(?:a|(?:a*))$`, notOnePass},
|
||||
{`^(?:(a)|(?:a*))$`, notOnePass},
|
||||
{`^(?:(?:(?:.(?:$))?))$`, onePass},
|
||||
{`^abcd$`, onePass},
|
||||
{`^(?:(?:a{0,})*?)$`, onePass},
|
||||
{`^(?:(?:a+)*)$`, onePass},
|
||||
{`^(?:(?:a|(?:aa)))$`, onePass},
|
||||
{`^(?:[^\s\S])$`, onePass},
|
||||
{`^(?:(?:a{3,4}){0,})$`, notOnePass},
|
||||
{`^(?:(?:(?:a*)+))$`, onePass},
|
||||
{`^[a-c]+$`, onePass},
|
||||
{`^[a-c]*$`, onePass},
|
||||
{`^(?:a*)$`, onePass},
|
||||
{`^(?:(?:aa)|a)$`, onePass},
|
||||
{`^[a-c]*`, notOnePass},
|
||||
{`^...$`, onePass},
|
||||
{`^(?:a|(?:aa))$`, onePass},
|
||||
{`^a((b))c$`, onePass},
|
||||
{`^a.[l-nA-Cg-j]?e$`, onePass},
|
||||
{`^a((b))$`, onePass},
|
||||
{`^a(?:(b)|(c))c$`, onePass},
|
||||
{`^a(?:(b*)|(c))c$`, notOnePass},
|
||||
{`^a(?:b|c)$`, onePass},
|
||||
{`^a(?:b?|c)$`, onePass},
|
||||
{`^a(?:b?|c?)$`, notOnePass},
|
||||
{`^a(?:b?|c+)$`, onePass},
|
||||
{`^a(?:b+|(bc))d$`, notOnePass},
|
||||
{`^a(?:bc)+$`, onePass},
|
||||
{`^a(?:[bcd])+$`, onePass},
|
||||
{`^a((?:[bcd])+)$`, onePass},
|
||||
{`^a(:?b|c)*d$`, onePass},
|
||||
{`^.bc(d|e)*$`, onePass},
|
||||
{`^(?:(?:aa)|.)$`, notOnePass},
|
||||
{`^(?:(?:a{1,2}){1,2})$`, notOnePass},
|
||||
{`^l` + strings.Repeat("o", 2<<8) + `ng$`, onePass},
|
||||
{`^(?:a|(?:a*))$`, false},
|
||||
{`^(?:(a)|(?:a*))$`, false},
|
||||
{`^(?:(?:(?:.(?:$))?))$`, true},
|
||||
{`^abcd$`, true},
|
||||
{`^(?:(?:a{0,})*?)$`, true},
|
||||
{`^(?:(?:a+)*)$`, true},
|
||||
{`^(?:(?:a|(?:aa)))$`, true},
|
||||
{`^(?:[^\s\S])$`, true},
|
||||
{`^(?:(?:a{3,4}){0,})$`, false},
|
||||
{`^(?:(?:(?:a*)+))$`, true},
|
||||
{`^[a-c]+$`, true},
|
||||
{`^[a-c]*$`, true},
|
||||
{`^(?:a*)$`, true},
|
||||
{`^(?:(?:aa)|a)$`, true},
|
||||
{`^[a-c]*`, false},
|
||||
{`^...$`, true},
|
||||
{`^(?:a|(?:aa))$`, true},
|
||||
{`^a((b))c$`, true},
|
||||
{`^a.[l-nA-Cg-j]?e$`, true},
|
||||
{`^a((b))$`, true},
|
||||
{`^a(?:(b)|(c))c$`, true},
|
||||
{`^a(?:(b*)|(c))c$`, false},
|
||||
{`^a(?:b|c)$`, true},
|
||||
{`^a(?:b?|c)$`, true},
|
||||
{`^a(?:b?|c?)$`, false},
|
||||
{`^a(?:b?|c+)$`, true},
|
||||
{`^a(?:b+|(bc))d$`, false},
|
||||
{`^a(?:bc)+$`, true},
|
||||
{`^a(?:[bcd])+$`, true},
|
||||
{`^a((?:[bcd])+)$`, true},
|
||||
{`^a(:?b|c)*d$`, true},
|
||||
{`^.bc(d|e)*$`, true},
|
||||
{`^(?:(?:aa)|.)$`, false},
|
||||
{`^(?:(?:a{1,2}){1,2})$`, false},
|
||||
{`^l` + strings.Repeat("o", 2<<8) + `ng$`, true},
|
||||
}
|
||||
|
||||
func TestCompileOnePass(t *testing.T) {
|
||||
@ -194,9 +192,9 @@ func TestCompileOnePass(t *testing.T) {
|
||||
t.Errorf("Compile(%q) got err:%s, want success", test.re, err)
|
||||
continue
|
||||
}
|
||||
onePass = compileOnePass(p)
|
||||
if (onePass == notOnePass) != (test.onePass == notOnePass) {
|
||||
t.Errorf("CompileOnePass(%q) got %v, expected %v", test.re, onePass, test.onePass)
|
||||
isOnePass := compileOnePass(p) != nil
|
||||
if isOnePass != test.isOnePass {
|
||||
t.Errorf("CompileOnePass(%q) got isOnePass=%v, expected %v", test.re, isOnePass, test.isOnePass)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -216,8 +214,8 @@ func TestRunOnePass(t *testing.T) {
|
||||
t.Errorf("Compile(%q): got err: %s", test.re, err)
|
||||
continue
|
||||
}
|
||||
if re.onepass == notOnePass {
|
||||
t.Errorf("Compile(%q): got notOnePass, want one-pass", test.re)
|
||||
if re.onepass == nil {
|
||||
t.Errorf("Compile(%q): got nil, want one-pass", test.re)
|
||||
continue
|
||||
}
|
||||
if !re.MatchString(test.match) {
|
||||
|
@ -191,7 +191,7 @@ func compile(expr string, mode syntax.Flags, longest bool) (*Regexp, error) {
|
||||
longest: longest,
|
||||
},
|
||||
}
|
||||
if regexp.onepass == notOnePass {
|
||||
if regexp.onepass == nil {
|
||||
regexp.prefix, regexp.prefixComplete = prog.Prefix()
|
||||
regexp.maxBitStateLen = maxBitStateLen(prog)
|
||||
} else {
|
||||
@ -218,7 +218,7 @@ func (re *Regexp) get() *machine {
|
||||
return z
|
||||
}
|
||||
re.mu.Unlock()
|
||||
z := progMachine(re.prog, re.onepass)
|
||||
z := progMachine(re.prog)
|
||||
z.re = re
|
||||
return z
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user