Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1634 lines
35 KiB
1634 lines
35 KiB
package regexp2 |
|
|
|
import ( |
|
"bytes" |
|
"errors" |
|
"fmt" |
|
"math" |
|
"strconv" |
|
"strings" |
|
"time" |
|
"unicode" |
|
|
|
"github.com/dlclark/regexp2/syntax" |
|
) |
|
|
|
type runner struct { |
|
re *Regexp |
|
code *syntax.Code |
|
|
|
runtextstart int // starting point for search |
|
|
|
runtext []rune // text to search |
|
runtextpos int // current position in text |
|
runtextend int |
|
|
|
// The backtracking stack. Opcodes use this to store data regarding |
|
// what they have matched and where to backtrack to. Each "frame" on |
|
// the stack takes the form of [CodePosition Data1 Data2...], where |
|
// CodePosition is the position of the current opcode and |
|
// the data values are all optional. The CodePosition can be negative, and |
|
// these values (also called "back2") are used by the BranchMark family of opcodes |
|
// to indicate whether they are backtracking after a successful or failed |
|
// match. |
|
// When we backtrack, we pop the CodePosition off the stack, set the current |
|
// instruction pointer to that code position, and mark the opcode |
|
// with a backtracking flag ("Back"). Each opcode then knows how to |
|
// handle its own data. |
|
runtrack []int |
|
runtrackpos int |
|
|
|
// This stack is used to track text positions across different opcodes. |
|
// For example, in /(a*b)+/, the parentheses result in a SetMark/CaptureMark |
|
// pair. SetMark records the text position before we match a*b. Then |
|
// CaptureMark uses that position to figure out where the capture starts. |
|
// Opcodes which push onto this stack are always paired with other opcodes |
|
// which will pop the value from it later. A successful match should mean |
|
// that this stack is empty. |
|
runstack []int |
|
runstackpos int |
|
|
|
// The crawl stack is used to keep track of captures. Every time a group |
|
// has a capture, we push its group number onto the runcrawl stack. In |
|
// the case of a balanced match, we push BOTH groups onto the stack. |
|
runcrawl []int |
|
runcrawlpos int |
|
|
|
runtrackcount int // count of states that may do backtracking |
|
|
|
runmatch *Match // result object |
|
|
|
ignoreTimeout bool |
|
timeout time.Duration // timeout in milliseconds (needed for actual) |
|
timeoutChecksToSkip int |
|
timeoutAt time.Time |
|
|
|
operator syntax.InstOp |
|
codepos int |
|
rightToLeft bool |
|
caseInsensitive bool |
|
} |
|
|
|
// run searches for matches and can continue from the previous match |
|
// |
|
// quick is usually false, but can be true to not return matches, just put it in caches |
|
// textstart is -1 to start at the "beginning" (depending on Right-To-Left), otherwise an index in input |
|
// input is the string to search for our regex pattern |
|
func (re *Regexp) run(quick bool, textstart int, input []rune) (*Match, error) { |
|
|
|
// get a cached runner |
|
runner := re.getRunner() |
|
defer re.putRunner(runner) |
|
|
|
if textstart < 0 { |
|
if re.RightToLeft() { |
|
textstart = len(input) |
|
} else { |
|
textstart = 0 |
|
} |
|
} |
|
|
|
return runner.scan(input, textstart, quick, re.MatchTimeout) |
|
} |
|
|
|
// Scans the string to find the first match. Uses the Match object |
|
// both to feed text in and as a place to store matches that come out. |
|
// |
|
// All the action is in the Go() method. Our |
|
// responsibility is to load up the class members before |
|
// calling Go. |
|
// |
|
// The optimizer can compute a set of candidate starting characters, |
|
// and we could use a separate method Skip() that will quickly scan past |
|
// any characters that we know can't match. |
|
func (r *runner) scan(rt []rune, textstart int, quick bool, timeout time.Duration) (*Match, error) { |
|
r.timeout = timeout |
|
r.ignoreTimeout = (time.Duration(math.MaxInt64) == timeout) |
|
r.runtextstart = textstart |
|
r.runtext = rt |
|
r.runtextend = len(rt) |
|
|
|
stoppos := r.runtextend |
|
bump := 1 |
|
|
|
if r.re.RightToLeft() { |
|
bump = -1 |
|
stoppos = 0 |
|
} |
|
|
|
r.runtextpos = textstart |
|
initted := false |
|
|
|
r.startTimeoutWatch() |
|
for { |
|
if r.re.Debug() { |
|
//fmt.Printf("\nSearch content: %v\n", string(r.runtext)) |
|
fmt.Printf("\nSearch range: from 0 to %v\n", r.runtextend) |
|
fmt.Printf("Firstchar search starting at %v stopping at %v\n", r.runtextpos, stoppos) |
|
} |
|
|
|
if r.findFirstChar() { |
|
if err := r.checkTimeout(); err != nil { |
|
return nil, err |
|
} |
|
|
|
if !initted { |
|
r.initMatch() |
|
initted = true |
|
} |
|
|
|
if r.re.Debug() { |
|
fmt.Printf("Executing engine starting at %v\n\n", r.runtextpos) |
|
} |
|
|
|
if err := r.execute(); err != nil { |
|
return nil, err |
|
} |
|
|
|
if r.runmatch.matchcount[0] > 0 { |
|
// We'll return a match even if it touches a previous empty match |
|
return r.tidyMatch(quick), nil |
|
} |
|
|
|
// reset state for another go |
|
r.runtrackpos = len(r.runtrack) |
|
r.runstackpos = len(r.runstack) |
|
r.runcrawlpos = len(r.runcrawl) |
|
} |
|
|
|
// failure! |
|
|
|
if r.runtextpos == stoppos { |
|
r.tidyMatch(true) |
|
return nil, nil |
|
} |
|
|
|
// Recognize leading []* and various anchors, and bump on failure accordingly |
|
|
|
// r.bump by one and start again |
|
|
|
r.runtextpos += bump |
|
} |
|
// We never get here |
|
} |
|
|
|
func (r *runner) execute() error { |
|
|
|
r.goTo(0) |
|
|
|
for { |
|
|
|
if r.re.Debug() { |
|
r.dumpState() |
|
} |
|
|
|
if err := r.checkTimeout(); err != nil { |
|
return err |
|
} |
|
|
|
switch r.operator { |
|
case syntax.Stop: |
|
return nil |
|
|
|
case syntax.Nothing: |
|
break |
|
|
|
case syntax.Goto: |
|
r.goTo(r.operand(0)) |
|
continue |
|
|
|
case syntax.Testref: |
|
if !r.runmatch.isMatched(r.operand(0)) { |
|
break |
|
} |
|
r.advance(1) |
|
continue |
|
|
|
case syntax.Lazybranch: |
|
r.trackPush1(r.textPos()) |
|
r.advance(1) |
|
continue |
|
|
|
case syntax.Lazybranch | syntax.Back: |
|
r.trackPop() |
|
r.textto(r.trackPeek()) |
|
r.goTo(r.operand(0)) |
|
continue |
|
|
|
case syntax.Setmark: |
|
r.stackPush(r.textPos()) |
|
r.trackPush() |
|
r.advance(0) |
|
continue |
|
|
|
case syntax.Nullmark: |
|
r.stackPush(-1) |
|
r.trackPush() |
|
r.advance(0) |
|
continue |
|
|
|
case syntax.Setmark | syntax.Back, syntax.Nullmark | syntax.Back: |
|
r.stackPop() |
|
break |
|
|
|
case syntax.Getmark: |
|
r.stackPop() |
|
r.trackPush1(r.stackPeek()) |
|
r.textto(r.stackPeek()) |
|
r.advance(0) |
|
continue |
|
|
|
case syntax.Getmark | syntax.Back: |
|
r.trackPop() |
|
r.stackPush(r.trackPeek()) |
|
break |
|
|
|
case syntax.Capturemark: |
|
if r.operand(1) != -1 && !r.runmatch.isMatched(r.operand(1)) { |
|
break |
|
} |
|
r.stackPop() |
|
if r.operand(1) != -1 { |
|
r.transferCapture(r.operand(0), r.operand(1), r.stackPeek(), r.textPos()) |
|
} else { |
|
r.capture(r.operand(0), r.stackPeek(), r.textPos()) |
|
} |
|
r.trackPush1(r.stackPeek()) |
|
|
|
r.advance(2) |
|
|
|
continue |
|
|
|
case syntax.Capturemark | syntax.Back: |
|
r.trackPop() |
|
r.stackPush(r.trackPeek()) |
|
r.uncapture() |
|
if r.operand(0) != -1 && r.operand(1) != -1 { |
|
r.uncapture() |
|
} |
|
|
|
break |
|
|
|
case syntax.Branchmark: |
|
r.stackPop() |
|
|
|
matched := r.textPos() - r.stackPeek() |
|
|
|
if matched != 0 { // Nonempty match -> loop now |
|
r.trackPush2(r.stackPeek(), r.textPos()) // Save old mark, textpos |
|
r.stackPush(r.textPos()) // Make new mark |
|
r.goTo(r.operand(0)) // Loop |
|
} else { // Empty match -> straight now |
|
r.trackPushNeg1(r.stackPeek()) // Save old mark |
|
r.advance(1) // Straight |
|
} |
|
continue |
|
|
|
case syntax.Branchmark | syntax.Back: |
|
r.trackPopN(2) |
|
r.stackPop() |
|
r.textto(r.trackPeekN(1)) // Recall position |
|
r.trackPushNeg1(r.trackPeek()) // Save old mark |
|
r.advance(1) // Straight |
|
continue |
|
|
|
case syntax.Branchmark | syntax.Back2: |
|
r.trackPop() |
|
r.stackPush(r.trackPeek()) // Recall old mark |
|
break // Backtrack |
|
|
|
case syntax.Lazybranchmark: |
|
{ |
|
// We hit this the first time through a lazy loop and after each |
|
// successful match of the inner expression. It simply continues |
|
// on and doesn't loop. |
|
r.stackPop() |
|
|
|
oldMarkPos := r.stackPeek() |
|
|
|
if r.textPos() != oldMarkPos { // Nonempty match -> try to loop again by going to 'back' state |
|
if oldMarkPos != -1 { |
|
r.trackPush2(oldMarkPos, r.textPos()) // Save old mark, textpos |
|
} else { |
|
r.trackPush2(r.textPos(), r.textPos()) |
|
} |
|
} else { |
|
// The inner expression found an empty match, so we'll go directly to 'back2' if we |
|
// backtrack. In this case, we need to push something on the stack, since back2 pops. |
|
// However, in the case of ()+? or similar, this empty match may be legitimate, so push the text |
|
// position associated with that empty match. |
|
r.stackPush(oldMarkPos) |
|
|
|
r.trackPushNeg1(r.stackPeek()) // Save old mark |
|
} |
|
r.advance(1) |
|
continue |
|
} |
|
|
|
case syntax.Lazybranchmark | syntax.Back: |
|
|
|
// After the first time, Lazybranchmark | syntax.Back occurs |
|
// with each iteration of the loop, and therefore with every attempted |
|
// match of the inner expression. We'll try to match the inner expression, |
|
// then go back to Lazybranchmark if successful. If the inner expression |
|
// fails, we go to Lazybranchmark | syntax.Back2 |
|
|
|
r.trackPopN(2) |
|
pos := r.trackPeekN(1) |
|
r.trackPushNeg1(r.trackPeek()) // Save old mark |
|
r.stackPush(pos) // Make new mark |
|
r.textto(pos) // Recall position |
|
r.goTo(r.operand(0)) // Loop |
|
continue |
|
|
|
case syntax.Lazybranchmark | syntax.Back2: |
|
// The lazy loop has failed. We'll do a true backtrack and |
|
// start over before the lazy loop. |
|
r.stackPop() |
|
r.trackPop() |
|
r.stackPush(r.trackPeek()) // Recall old mark |
|
break |
|
|
|
case syntax.Setcount: |
|
r.stackPush2(r.textPos(), r.operand(0)) |
|
r.trackPush() |
|
r.advance(1) |
|
continue |
|
|
|
case syntax.Nullcount: |
|
r.stackPush2(-1, r.operand(0)) |
|
r.trackPush() |
|
r.advance(1) |
|
continue |
|
|
|
case syntax.Setcount | syntax.Back: |
|
r.stackPopN(2) |
|
break |
|
|
|
case syntax.Nullcount | syntax.Back: |
|
r.stackPopN(2) |
|
break |
|
|
|
case syntax.Branchcount: |
|
// r.stackPush: |
|
// 0: Mark |
|
// 1: Count |
|
|
|
r.stackPopN(2) |
|
mark := r.stackPeek() |
|
count := r.stackPeekN(1) |
|
matched := r.textPos() - mark |
|
|
|
if count >= r.operand(1) || (matched == 0 && count >= 0) { // Max loops or empty match -> straight now |
|
r.trackPushNeg2(mark, count) // Save old mark, count |
|
r.advance(2) // Straight |
|
} else { // Nonempty match -> count+loop now |
|
r.trackPush1(mark) // remember mark |
|
r.stackPush2(r.textPos(), count+1) // Make new mark, incr count |
|
r.goTo(r.operand(0)) // Loop |
|
} |
|
continue |
|
|
|
case syntax.Branchcount | syntax.Back: |
|
// r.trackPush: |
|
// 0: Previous mark |
|
// r.stackPush: |
|
// 0: Mark (= current pos, discarded) |
|
// 1: Count |
|
r.trackPop() |
|
r.stackPopN(2) |
|
if r.stackPeekN(1) > 0 { // Positive -> can go straight |
|
r.textto(r.stackPeek()) // Zap to mark |
|
r.trackPushNeg2(r.trackPeek(), r.stackPeekN(1)-1) // Save old mark, old count |
|
r.advance(2) // Straight |
|
continue |
|
} |
|
r.stackPush2(r.trackPeek(), r.stackPeekN(1)-1) // recall old mark, old count |
|
break |
|
|
|
case syntax.Branchcount | syntax.Back2: |
|
// r.trackPush: |
|
// 0: Previous mark |
|
// 1: Previous count |
|
r.trackPopN(2) |
|
r.stackPush2(r.trackPeek(), r.trackPeekN(1)) // Recall old mark, old count |
|
break // Backtrack |
|
|
|
case syntax.Lazybranchcount: |
|
// r.stackPush: |
|
// 0: Mark |
|
// 1: Count |
|
|
|
r.stackPopN(2) |
|
mark := r.stackPeek() |
|
count := r.stackPeekN(1) |
|
|
|
if count < 0 { // Negative count -> loop now |
|
r.trackPushNeg1(mark) // Save old mark |
|
r.stackPush2(r.textPos(), count+1) // Make new mark, incr count |
|
r.goTo(r.operand(0)) // Loop |
|
} else { // Nonneg count -> straight now |
|
r.trackPush3(mark, count, r.textPos()) // Save mark, count, position |
|
r.advance(2) // Straight |
|
} |
|
continue |
|
|
|
case syntax.Lazybranchcount | syntax.Back: |
|
// r.trackPush: |
|
// 0: Mark |
|
// 1: Count |
|
// 2: r.textPos |
|
|
|
r.trackPopN(3) |
|
mark := r.trackPeek() |
|
textpos := r.trackPeekN(2) |
|
|
|
if r.trackPeekN(1) < r.operand(1) && textpos != mark { // Under limit and not empty match -> loop |
|
r.textto(textpos) // Recall position |
|
r.stackPush2(textpos, r.trackPeekN(1)+1) // Make new mark, incr count |
|
r.trackPushNeg1(mark) // Save old mark |
|
r.goTo(r.operand(0)) // Loop |
|
continue |
|
} else { // Max loops or empty match -> backtrack |
|
r.stackPush2(r.trackPeek(), r.trackPeekN(1)) // Recall old mark, count |
|
break // backtrack |
|
} |
|
|
|
case syntax.Lazybranchcount | syntax.Back2: |
|
// r.trackPush: |
|
// 0: Previous mark |
|
// r.stackPush: |
|
// 0: Mark (== current pos, discarded) |
|
// 1: Count |
|
r.trackPop() |
|
r.stackPopN(2) |
|
r.stackPush2(r.trackPeek(), r.stackPeekN(1)-1) // Recall old mark, count |
|
break // Backtrack |
|
|
|
case syntax.Setjump: |
|
r.stackPush2(r.trackpos(), r.crawlpos()) |
|
r.trackPush() |
|
r.advance(0) |
|
continue |
|
|
|
case syntax.Setjump | syntax.Back: |
|
r.stackPopN(2) |
|
break |
|
|
|
case syntax.Backjump: |
|
// r.stackPush: |
|
// 0: Saved trackpos |
|
// 1: r.crawlpos |
|
r.stackPopN(2) |
|
r.trackto(r.stackPeek()) |
|
|
|
for r.crawlpos() != r.stackPeekN(1) { |
|
r.uncapture() |
|
} |
|
|
|
break |
|
|
|
case syntax.Forejump: |
|
// r.stackPush: |
|
// 0: Saved trackpos |
|
// 1: r.crawlpos |
|
r.stackPopN(2) |
|
r.trackto(r.stackPeek()) |
|
r.trackPush1(r.stackPeekN(1)) |
|
r.advance(0) |
|
continue |
|
|
|
case syntax.Forejump | syntax.Back: |
|
// r.trackPush: |
|
// 0: r.crawlpos |
|
r.trackPop() |
|
|
|
for r.crawlpos() != r.trackPeek() { |
|
r.uncapture() |
|
} |
|
|
|
break |
|
|
|
case syntax.Bol: |
|
if r.leftchars() > 0 && r.charAt(r.textPos()-1) != '\n' { |
|
break |
|
} |
|
r.advance(0) |
|
continue |
|
|
|
case syntax.Eol: |
|
if r.rightchars() > 0 && r.charAt(r.textPos()) != '\n' { |
|
break |
|
} |
|
r.advance(0) |
|
continue |
|
|
|
case syntax.Boundary: |
|
if !r.isBoundary(r.textPos(), 0, r.runtextend) { |
|
break |
|
} |
|
r.advance(0) |
|
continue |
|
|
|
case syntax.Nonboundary: |
|
if r.isBoundary(r.textPos(), 0, r.runtextend) { |
|
break |
|
} |
|
r.advance(0) |
|
continue |
|
|
|
case syntax.ECMABoundary: |
|
if !r.isECMABoundary(r.textPos(), 0, r.runtextend) { |
|
break |
|
} |
|
r.advance(0) |
|
continue |
|
|
|
case syntax.NonECMABoundary: |
|
if r.isECMABoundary(r.textPos(), 0, r.runtextend) { |
|
break |
|
} |
|
r.advance(0) |
|
continue |
|
|
|
case syntax.Beginning: |
|
if r.leftchars() > 0 { |
|
break |
|
} |
|
r.advance(0) |
|
continue |
|
|
|
case syntax.Start: |
|
if r.textPos() != r.textstart() { |
|
break |
|
} |
|
r.advance(0) |
|
continue |
|
|
|
case syntax.EndZ: |
|
rchars := r.rightchars() |
|
if rchars > 1 { |
|
break |
|
} |
|
// RE2 and EcmaScript define $ as "asserts position at the end of the string" |
|
// PCRE/.NET adds "or before the line terminator right at the end of the string (if any)" |
|
if (r.re.options & (RE2 | ECMAScript)) != 0 { |
|
// RE2/Ecmascript mode |
|
if rchars > 0 { |
|
break |
|
} |
|
} else if rchars == 1 && r.charAt(r.textPos()) != '\n' { |
|
// "regular" mode |
|
break |
|
} |
|
|
|
r.advance(0) |
|
continue |
|
|
|
case syntax.End: |
|
if r.rightchars() > 0 { |
|
break |
|
} |
|
r.advance(0) |
|
continue |
|
|
|
case syntax.One: |
|
if r.forwardchars() < 1 || r.forwardcharnext() != rune(r.operand(0)) { |
|
break |
|
} |
|
|
|
r.advance(1) |
|
continue |
|
|
|
case syntax.Notone: |
|
if r.forwardchars() < 1 || r.forwardcharnext() == rune(r.operand(0)) { |
|
break |
|
} |
|
|
|
r.advance(1) |
|
continue |
|
|
|
case syntax.Set: |
|
|
|
if r.forwardchars() < 1 || !r.code.Sets[r.operand(0)].CharIn(r.forwardcharnext()) { |
|
break |
|
} |
|
|
|
r.advance(1) |
|
continue |
|
|
|
case syntax.Multi: |
|
if !r.runematch(r.code.Strings[r.operand(0)]) { |
|
break |
|
} |
|
|
|
r.advance(1) |
|
continue |
|
|
|
case syntax.Ref: |
|
|
|
capnum := r.operand(0) |
|
|
|
if r.runmatch.isMatched(capnum) { |
|
if !r.refmatch(r.runmatch.matchIndex(capnum), r.runmatch.matchLength(capnum)) { |
|
break |
|
} |
|
} else { |
|
if (r.re.options & ECMAScript) == 0 { |
|
break |
|
} |
|
} |
|
|
|
r.advance(1) |
|
continue |
|
|
|
case syntax.Onerep: |
|
|
|
c := r.operand(1) |
|
|
|
if r.forwardchars() < c { |
|
break |
|
} |
|
|
|
ch := rune(r.operand(0)) |
|
|
|
for c > 0 { |
|
if r.forwardcharnext() != ch { |
|
goto BreakBackward |
|
} |
|
c-- |
|
} |
|
|
|
r.advance(2) |
|
continue |
|
|
|
case syntax.Notonerep: |
|
|
|
c := r.operand(1) |
|
|
|
if r.forwardchars() < c { |
|
break |
|
} |
|
ch := rune(r.operand(0)) |
|
|
|
for c > 0 { |
|
if r.forwardcharnext() == ch { |
|
goto BreakBackward |
|
} |
|
c-- |
|
} |
|
|
|
r.advance(2) |
|
continue |
|
|
|
case syntax.Setrep: |
|
|
|
c := r.operand(1) |
|
|
|
if r.forwardchars() < c { |
|
break |
|
} |
|
|
|
set := r.code.Sets[r.operand(0)] |
|
|
|
for c > 0 { |
|
if !set.CharIn(r.forwardcharnext()) { |
|
goto BreakBackward |
|
} |
|
c-- |
|
} |
|
|
|
r.advance(2) |
|
continue |
|
|
|
case syntax.Oneloop: |
|
|
|
c := r.operand(1) |
|
|
|
if c > r.forwardchars() { |
|
c = r.forwardchars() |
|
} |
|
|
|
ch := rune(r.operand(0)) |
|
i := c |
|
|
|
for ; i > 0; i-- { |
|
if r.forwardcharnext() != ch { |
|
r.backwardnext() |
|
break |
|
} |
|
} |
|
|
|
if c > i { |
|
r.trackPush2(c-i-1, r.textPos()-r.bump()) |
|
} |
|
|
|
r.advance(2) |
|
continue |
|
|
|
case syntax.Notoneloop: |
|
|
|
c := r.operand(1) |
|
|
|
if c > r.forwardchars() { |
|
c = r.forwardchars() |
|
} |
|
|
|
ch := rune(r.operand(0)) |
|
i := c |
|
|
|
for ; i > 0; i-- { |
|
if r.forwardcharnext() == ch { |
|
r.backwardnext() |
|
break |
|
} |
|
} |
|
|
|
if c > i { |
|
r.trackPush2(c-i-1, r.textPos()-r.bump()) |
|
} |
|
|
|
r.advance(2) |
|
continue |
|
|
|
case syntax.Setloop: |
|
|
|
c := r.operand(1) |
|
|
|
if c > r.forwardchars() { |
|
c = r.forwardchars() |
|
} |
|
|
|
set := r.code.Sets[r.operand(0)] |
|
i := c |
|
|
|
for ; i > 0; i-- { |
|
if !set.CharIn(r.forwardcharnext()) { |
|
r.backwardnext() |
|
break |
|
} |
|
} |
|
|
|
if c > i { |
|
r.trackPush2(c-i-1, r.textPos()-r.bump()) |
|
} |
|
|
|
r.advance(2) |
|
continue |
|
|
|
case syntax.Oneloop | syntax.Back, syntax.Notoneloop | syntax.Back: |
|
|
|
r.trackPopN(2) |
|
i := r.trackPeek() |
|
pos := r.trackPeekN(1) |
|
|
|
r.textto(pos) |
|
|
|
if i > 0 { |
|
r.trackPush2(i-1, pos-r.bump()) |
|
} |
|
|
|
r.advance(2) |
|
continue |
|
|
|
case syntax.Setloop | syntax.Back: |
|
|
|
r.trackPopN(2) |
|
i := r.trackPeek() |
|
pos := r.trackPeekN(1) |
|
|
|
r.textto(pos) |
|
|
|
if i > 0 { |
|
r.trackPush2(i-1, pos-r.bump()) |
|
} |
|
|
|
r.advance(2) |
|
continue |
|
|
|
case syntax.Onelazy, syntax.Notonelazy: |
|
|
|
c := r.operand(1) |
|
|
|
if c > r.forwardchars() { |
|
c = r.forwardchars() |
|
} |
|
|
|
if c > 0 { |
|
r.trackPush2(c-1, r.textPos()) |
|
} |
|
|
|
r.advance(2) |
|
continue |
|
|
|
case syntax.Setlazy: |
|
|
|
c := r.operand(1) |
|
|
|
if c > r.forwardchars() { |
|
c = r.forwardchars() |
|
} |
|
|
|
if c > 0 { |
|
r.trackPush2(c-1, r.textPos()) |
|
} |
|
|
|
r.advance(2) |
|
continue |
|
|
|
case syntax.Onelazy | syntax.Back: |
|
|
|
r.trackPopN(2) |
|
pos := r.trackPeekN(1) |
|
r.textto(pos) |
|
|
|
if r.forwardcharnext() != rune(r.operand(0)) { |
|
break |
|
} |
|
|
|
i := r.trackPeek() |
|
|
|
if i > 0 { |
|
r.trackPush2(i-1, pos+r.bump()) |
|
} |
|
|
|
r.advance(2) |
|
continue |
|
|
|
case syntax.Notonelazy | syntax.Back: |
|
|
|
r.trackPopN(2) |
|
pos := r.trackPeekN(1) |
|
r.textto(pos) |
|
|
|
if r.forwardcharnext() == rune(r.operand(0)) { |
|
break |
|
} |
|
|
|
i := r.trackPeek() |
|
|
|
if i > 0 { |
|
r.trackPush2(i-1, pos+r.bump()) |
|
} |
|
|
|
r.advance(2) |
|
continue |
|
|
|
case syntax.Setlazy | syntax.Back: |
|
|
|
r.trackPopN(2) |
|
pos := r.trackPeekN(1) |
|
r.textto(pos) |
|
|
|
if !r.code.Sets[r.operand(0)].CharIn(r.forwardcharnext()) { |
|
break |
|
} |
|
|
|
i := r.trackPeek() |
|
|
|
if i > 0 { |
|
r.trackPush2(i-1, pos+r.bump()) |
|
} |
|
|
|
r.advance(2) |
|
continue |
|
|
|
default: |
|
return errors.New("unknown state in regex runner") |
|
} |
|
|
|
BreakBackward: |
|
; |
|
|
|
// "break Backward" comes here: |
|
r.backtrack() |
|
} |
|
} |
|
|
|
// increase the size of stack and track storage |
|
func (r *runner) ensureStorage() { |
|
if r.runstackpos < r.runtrackcount*4 { |
|
doubleIntSlice(&r.runstack, &r.runstackpos) |
|
} |
|
if r.runtrackpos < r.runtrackcount*4 { |
|
doubleIntSlice(&r.runtrack, &r.runtrackpos) |
|
} |
|
} |
|
|
|
func doubleIntSlice(s *[]int, pos *int) { |
|
oldLen := len(*s) |
|
newS := make([]int, oldLen*2) |
|
|
|
copy(newS[oldLen:], *s) |
|
*pos += oldLen |
|
*s = newS |
|
} |
|
|
|
// Save a number on the longjump unrolling stack |
|
func (r *runner) crawl(i int) { |
|
if r.runcrawlpos == 0 { |
|
doubleIntSlice(&r.runcrawl, &r.runcrawlpos) |
|
} |
|
r.runcrawlpos-- |
|
r.runcrawl[r.runcrawlpos] = i |
|
} |
|
|
|
// Remove a number from the longjump unrolling stack |
|
func (r *runner) popcrawl() int { |
|
val := r.runcrawl[r.runcrawlpos] |
|
r.runcrawlpos++ |
|
return val |
|
} |
|
|
|
// Get the height of the stack |
|
func (r *runner) crawlpos() int { |
|
return len(r.runcrawl) - r.runcrawlpos |
|
} |
|
|
|
func (r *runner) advance(i int) { |
|
r.codepos += (i + 1) |
|
r.setOperator(r.code.Codes[r.codepos]) |
|
} |
|
|
|
func (r *runner) goTo(newpos int) { |
|
// when branching backward or in place, ensure storage |
|
if newpos <= r.codepos { |
|
r.ensureStorage() |
|
} |
|
|
|
r.setOperator(r.code.Codes[newpos]) |
|
r.codepos = newpos |
|
} |
|
|
|
func (r *runner) textto(newpos int) { |
|
r.runtextpos = newpos |
|
} |
|
|
|
func (r *runner) trackto(newpos int) { |
|
r.runtrackpos = len(r.runtrack) - newpos |
|
} |
|
|
|
func (r *runner) textstart() int { |
|
return r.runtextstart |
|
} |
|
|
|
func (r *runner) textPos() int { |
|
return r.runtextpos |
|
} |
|
|
|
// push onto the backtracking stack |
|
func (r *runner) trackpos() int { |
|
return len(r.runtrack) - r.runtrackpos |
|
} |
|
|
|
func (r *runner) trackPush() { |
|
r.runtrackpos-- |
|
r.runtrack[r.runtrackpos] = r.codepos |
|
} |
|
|
|
func (r *runner) trackPush1(I1 int) { |
|
r.runtrackpos-- |
|
r.runtrack[r.runtrackpos] = I1 |
|
r.runtrackpos-- |
|
r.runtrack[r.runtrackpos] = r.codepos |
|
} |
|
|
|
func (r *runner) trackPush2(I1, I2 int) { |
|
r.runtrackpos-- |
|
r.runtrack[r.runtrackpos] = I1 |
|
r.runtrackpos-- |
|
r.runtrack[r.runtrackpos] = I2 |
|
r.runtrackpos-- |
|
r.runtrack[r.runtrackpos] = r.codepos |
|
} |
|
|
|
func (r *runner) trackPush3(I1, I2, I3 int) { |
|
r.runtrackpos-- |
|
r.runtrack[r.runtrackpos] = I1 |
|
r.runtrackpos-- |
|
r.runtrack[r.runtrackpos] = I2 |
|
r.runtrackpos-- |
|
r.runtrack[r.runtrackpos] = I3 |
|
r.runtrackpos-- |
|
r.runtrack[r.runtrackpos] = r.codepos |
|
} |
|
|
|
func (r *runner) trackPushNeg1(I1 int) { |
|
r.runtrackpos-- |
|
r.runtrack[r.runtrackpos] = I1 |
|
r.runtrackpos-- |
|
r.runtrack[r.runtrackpos] = -r.codepos |
|
} |
|
|
|
func (r *runner) trackPushNeg2(I1, I2 int) { |
|
r.runtrackpos-- |
|
r.runtrack[r.runtrackpos] = I1 |
|
r.runtrackpos-- |
|
r.runtrack[r.runtrackpos] = I2 |
|
r.runtrackpos-- |
|
r.runtrack[r.runtrackpos] = -r.codepos |
|
} |
|
|
|
func (r *runner) backtrack() { |
|
newpos := r.runtrack[r.runtrackpos] |
|
r.runtrackpos++ |
|
|
|
if r.re.Debug() { |
|
if newpos < 0 { |
|
fmt.Printf(" Backtracking (back2) to code position %v\n", -newpos) |
|
} else { |
|
fmt.Printf(" Backtracking to code position %v\n", newpos) |
|
} |
|
} |
|
|
|
if newpos < 0 { |
|
newpos = -newpos |
|
r.setOperator(r.code.Codes[newpos] | syntax.Back2) |
|
} else { |
|
r.setOperator(r.code.Codes[newpos] | syntax.Back) |
|
} |
|
|
|
// When branching backward, ensure storage |
|
if newpos < r.codepos { |
|
r.ensureStorage() |
|
} |
|
|
|
r.codepos = newpos |
|
} |
|
|
|
func (r *runner) setOperator(op int) { |
|
r.caseInsensitive = (0 != (op & syntax.Ci)) |
|
r.rightToLeft = (0 != (op & syntax.Rtl)) |
|
r.operator = syntax.InstOp(op & ^(syntax.Rtl | syntax.Ci)) |
|
} |
|
|
|
func (r *runner) trackPop() { |
|
r.runtrackpos++ |
|
} |
|
|
|
// pop framesize items from the backtracking stack |
|
func (r *runner) trackPopN(framesize int) { |
|
r.runtrackpos += framesize |
|
} |
|
|
|
// Technically we are actually peeking at items already popped. So if you want to |
|
// get and pop the top item from the stack, you do |
|
// r.trackPop(); |
|
// r.trackPeek(); |
|
func (r *runner) trackPeek() int { |
|
return r.runtrack[r.runtrackpos-1] |
|
} |
|
|
|
// get the ith element down on the backtracking stack |
|
func (r *runner) trackPeekN(i int) int { |
|
return r.runtrack[r.runtrackpos-i-1] |
|
} |
|
|
|
// Push onto the grouping stack |
|
func (r *runner) stackPush(I1 int) { |
|
r.runstackpos-- |
|
r.runstack[r.runstackpos] = I1 |
|
} |
|
|
|
func (r *runner) stackPush2(I1, I2 int) { |
|
r.runstackpos-- |
|
r.runstack[r.runstackpos] = I1 |
|
r.runstackpos-- |
|
r.runstack[r.runstackpos] = I2 |
|
} |
|
|
|
func (r *runner) stackPop() { |
|
r.runstackpos++ |
|
} |
|
|
|
// pop framesize items from the grouping stack |
|
func (r *runner) stackPopN(framesize int) { |
|
r.runstackpos += framesize |
|
} |
|
|
|
// Technically we are actually peeking at items already popped. So if you want to |
|
// get and pop the top item from the stack, you do |
|
// r.stackPop(); |
|
// r.stackPeek(); |
|
func (r *runner) stackPeek() int { |
|
return r.runstack[r.runstackpos-1] |
|
} |
|
|
|
// get the ith element down on the grouping stack |
|
func (r *runner) stackPeekN(i int) int { |
|
return r.runstack[r.runstackpos-i-1] |
|
} |
|
|
|
func (r *runner) operand(i int) int { |
|
return r.code.Codes[r.codepos+i+1] |
|
} |
|
|
|
func (r *runner) leftchars() int { |
|
return r.runtextpos |
|
} |
|
|
|
func (r *runner) rightchars() int { |
|
return r.runtextend - r.runtextpos |
|
} |
|
|
|
func (r *runner) bump() int { |
|
if r.rightToLeft { |
|
return -1 |
|
} |
|
return 1 |
|
} |
|
|
|
func (r *runner) forwardchars() int { |
|
if r.rightToLeft { |
|
return r.runtextpos |
|
} |
|
return r.runtextend - r.runtextpos |
|
} |
|
|
|
func (r *runner) forwardcharnext() rune { |
|
var ch rune |
|
if r.rightToLeft { |
|
r.runtextpos-- |
|
ch = r.runtext[r.runtextpos] |
|
} else { |
|
ch = r.runtext[r.runtextpos] |
|
r.runtextpos++ |
|
} |
|
|
|
if r.caseInsensitive { |
|
return unicode.ToLower(ch) |
|
} |
|
return ch |
|
} |
|
|
|
func (r *runner) runematch(str []rune) bool { |
|
var pos int |
|
|
|
c := len(str) |
|
if !r.rightToLeft { |
|
if r.runtextend-r.runtextpos < c { |
|
return false |
|
} |
|
|
|
pos = r.runtextpos + c |
|
} else { |
|
if r.runtextpos-0 < c { |
|
return false |
|
} |
|
|
|
pos = r.runtextpos |
|
} |
|
|
|
if !r.caseInsensitive { |
|
for c != 0 { |
|
c-- |
|
pos-- |
|
if str[c] != r.runtext[pos] { |
|
return false |
|
} |
|
} |
|
} else { |
|
for c != 0 { |
|
c-- |
|
pos-- |
|
if str[c] != unicode.ToLower(r.runtext[pos]) { |
|
return false |
|
} |
|
} |
|
} |
|
|
|
if !r.rightToLeft { |
|
pos += len(str) |
|
} |
|
|
|
r.runtextpos = pos |
|
|
|
return true |
|
} |
|
|
|
func (r *runner) refmatch(index, len int) bool { |
|
var c, pos, cmpos int |
|
|
|
if !r.rightToLeft { |
|
if r.runtextend-r.runtextpos < len { |
|
return false |
|
} |
|
|
|
pos = r.runtextpos + len |
|
} else { |
|
if r.runtextpos-0 < len { |
|
return false |
|
} |
|
|
|
pos = r.runtextpos |
|
} |
|
cmpos = index + len |
|
|
|
c = len |
|
|
|
if !r.caseInsensitive { |
|
for c != 0 { |
|
c-- |
|
cmpos-- |
|
pos-- |
|
if r.runtext[cmpos] != r.runtext[pos] { |
|
return false |
|
} |
|
|
|
} |
|
} else { |
|
for c != 0 { |
|
c-- |
|
cmpos-- |
|
pos-- |
|
|
|
if unicode.ToLower(r.runtext[cmpos]) != unicode.ToLower(r.runtext[pos]) { |
|
return false |
|
} |
|
} |
|
} |
|
|
|
if !r.rightToLeft { |
|
pos += len |
|
} |
|
|
|
r.runtextpos = pos |
|
|
|
return true |
|
} |
|
|
|
func (r *runner) backwardnext() { |
|
if r.rightToLeft { |
|
r.runtextpos++ |
|
} else { |
|
r.runtextpos-- |
|
} |
|
} |
|
|
|
func (r *runner) charAt(j int) rune { |
|
return r.runtext[j] |
|
} |
|
|
|
func (r *runner) findFirstChar() bool { |
|
|
|
if 0 != (r.code.Anchors & (syntax.AnchorBeginning | syntax.AnchorStart | syntax.AnchorEndZ | syntax.AnchorEnd)) { |
|
if !r.code.RightToLeft { |
|
if (0 != (r.code.Anchors&syntax.AnchorBeginning) && r.runtextpos > 0) || |
|
(0 != (r.code.Anchors&syntax.AnchorStart) && r.runtextpos > r.runtextstart) { |
|
r.runtextpos = r.runtextend |
|
return false |
|
} |
|
if 0 != (r.code.Anchors&syntax.AnchorEndZ) && r.runtextpos < r.runtextend-1 { |
|
r.runtextpos = r.runtextend - 1 |
|
} else if 0 != (r.code.Anchors&syntax.AnchorEnd) && r.runtextpos < r.runtextend { |
|
r.runtextpos = r.runtextend |
|
} |
|
} else { |
|
if (0 != (r.code.Anchors&syntax.AnchorEnd) && r.runtextpos < r.runtextend) || |
|
(0 != (r.code.Anchors&syntax.AnchorEndZ) && (r.runtextpos < r.runtextend-1 || |
|
(r.runtextpos == r.runtextend-1 && r.charAt(r.runtextpos) != '\n'))) || |
|
(0 != (r.code.Anchors&syntax.AnchorStart) && r.runtextpos < r.runtextstart) { |
|
r.runtextpos = 0 |
|
return false |
|
} |
|
if 0 != (r.code.Anchors&syntax.AnchorBeginning) && r.runtextpos > 0 { |
|
r.runtextpos = 0 |
|
} |
|
} |
|
|
|
if r.code.BmPrefix != nil { |
|
return r.code.BmPrefix.IsMatch(r.runtext, r.runtextpos, 0, r.runtextend) |
|
} |
|
|
|
return true // found a valid start or end anchor |
|
} else if r.code.BmPrefix != nil { |
|
r.runtextpos = r.code.BmPrefix.Scan(r.runtext, r.runtextpos, 0, r.runtextend) |
|
|
|
if r.runtextpos == -1 { |
|
if r.code.RightToLeft { |
|
r.runtextpos = 0 |
|
} else { |
|
r.runtextpos = r.runtextend |
|
} |
|
return false |
|
} |
|
|
|
return true |
|
} else if r.code.FcPrefix == nil { |
|
return true |
|
} |
|
|
|
r.rightToLeft = r.code.RightToLeft |
|
r.caseInsensitive = r.code.FcPrefix.CaseInsensitive |
|
|
|
set := r.code.FcPrefix.PrefixSet |
|
if set.IsSingleton() { |
|
ch := set.SingletonChar() |
|
for i := r.forwardchars(); i > 0; i-- { |
|
if ch == r.forwardcharnext() { |
|
r.backwardnext() |
|
return true |
|
} |
|
} |
|
} else { |
|
for i := r.forwardchars(); i > 0; i-- { |
|
n := r.forwardcharnext() |
|
//fmt.Printf("%v in %v: %v\n", string(n), set.String(), set.CharIn(n)) |
|
if set.CharIn(n) { |
|
r.backwardnext() |
|
return true |
|
} |
|
} |
|
} |
|
|
|
return false |
|
} |
|
|
|
func (r *runner) initMatch() { |
|
// Use a hashtable'ed Match object if the capture numbers are sparse |
|
|
|
if r.runmatch == nil { |
|
if r.re.caps != nil { |
|
r.runmatch = newMatchSparse(r.re, r.re.caps, r.re.capsize, r.runtext, r.runtextstart) |
|
} else { |
|
r.runmatch = newMatch(r.re, r.re.capsize, r.runtext, r.runtextstart) |
|
} |
|
} else { |
|
r.runmatch.reset(r.runtext, r.runtextstart) |
|
} |
|
|
|
// note we test runcrawl, because it is the last one to be allocated |
|
// If there is an alloc failure in the middle of the three allocations, |
|
// we may still return to reuse this instance, and we want to behave |
|
// as if the allocations didn't occur. (we used to test _trackcount != 0) |
|
|
|
if r.runcrawl != nil { |
|
r.runtrackpos = len(r.runtrack) |
|
r.runstackpos = len(r.runstack) |
|
r.runcrawlpos = len(r.runcrawl) |
|
return |
|
} |
|
|
|
r.initTrackCount() |
|
|
|
tracksize := r.runtrackcount * 8 |
|
stacksize := r.runtrackcount * 8 |
|
|
|
if tracksize < 32 { |
|
tracksize = 32 |
|
} |
|
if stacksize < 16 { |
|
stacksize = 16 |
|
} |
|
|
|
r.runtrack = make([]int, tracksize) |
|
r.runtrackpos = tracksize |
|
|
|
r.runstack = make([]int, stacksize) |
|
r.runstackpos = stacksize |
|
|
|
r.runcrawl = make([]int, 32) |
|
r.runcrawlpos = 32 |
|
} |
|
|
|
func (r *runner) tidyMatch(quick bool) *Match { |
|
if !quick { |
|
match := r.runmatch |
|
|
|
r.runmatch = nil |
|
|
|
match.tidy(r.runtextpos) |
|
return match |
|
} else { |
|
// send back our match -- it's not leaving the package, so it's safe to not clean it up |
|
// this reduces allocs for frequent calls to the "IsMatch" bool-only functions |
|
return r.runmatch |
|
} |
|
} |
|
|
|
// capture captures a subexpression. Note that the |
|
// capnum used here has already been mapped to a non-sparse |
|
// index (by the code generator RegexWriter). |
|
func (r *runner) capture(capnum, start, end int) { |
|
if end < start { |
|
T := end |
|
end = start |
|
start = T |
|
} |
|
|
|
r.crawl(capnum) |
|
r.runmatch.addMatch(capnum, start, end-start) |
|
} |
|
|
|
// transferCapture captures a subexpression. Note that the |
|
// capnum used here has already been mapped to a non-sparse |
|
// index (by the code generator RegexWriter). |
|
func (r *runner) transferCapture(capnum, uncapnum, start, end int) { |
|
var start2, end2 int |
|
|
|
// these are the two intervals that are cancelling each other |
|
|
|
if end < start { |
|
T := end |
|
end = start |
|
start = T |
|
} |
|
|
|
start2 = r.runmatch.matchIndex(uncapnum) |
|
end2 = start2 + r.runmatch.matchLength(uncapnum) |
|
|
|
// The new capture gets the innermost defined interval |
|
|
|
if start >= end2 { |
|
end = start |
|
start = end2 |
|
} else if end <= start2 { |
|
start = start2 |
|
} else { |
|
if end > end2 { |
|
end = end2 |
|
} |
|
if start2 > start { |
|
start = start2 |
|
} |
|
} |
|
|
|
r.crawl(uncapnum) |
|
r.runmatch.balanceMatch(uncapnum) |
|
|
|
if capnum != -1 { |
|
r.crawl(capnum) |
|
r.runmatch.addMatch(capnum, start, end-start) |
|
} |
|
} |
|
|
|
// revert the last capture |
|
func (r *runner) uncapture() { |
|
capnum := r.popcrawl() |
|
r.runmatch.removeMatch(capnum) |
|
} |
|
|
|
//debug |
|
|
|
func (r *runner) dumpState() { |
|
back := "" |
|
if r.operator&syntax.Back != 0 { |
|
back = " Back" |
|
} |
|
if r.operator&syntax.Back2 != 0 { |
|
back += " Back2" |
|
} |
|
fmt.Printf("Text: %v\nTrack: %v\nStack: %v\n %s%s\n\n", |
|
r.textposDescription(), |
|
r.stackDescription(r.runtrack, r.runtrackpos), |
|
r.stackDescription(r.runstack, r.runstackpos), |
|
r.code.OpcodeDescription(r.codepos), |
|
back) |
|
} |
|
|
|
func (r *runner) stackDescription(a []int, index int) string { |
|
buf := &bytes.Buffer{} |
|
|
|
fmt.Fprintf(buf, "%v/%v", len(a)-index, len(a)) |
|
if buf.Len() < 8 { |
|
buf.WriteString(strings.Repeat(" ", 8-buf.Len())) |
|
} |
|
|
|
buf.WriteRune('(') |
|
for i := index; i < len(a); i++ { |
|
if i > index { |
|
buf.WriteRune(' ') |
|
} |
|
|
|
buf.WriteString(strconv.Itoa(a[i])) |
|
} |
|
|
|
buf.WriteRune(')') |
|
|
|
return buf.String() |
|
} |
|
|
|
func (r *runner) textposDescription() string { |
|
buf := &bytes.Buffer{} |
|
|
|
buf.WriteString(strconv.Itoa(r.runtextpos)) |
|
|
|
if buf.Len() < 8 { |
|
buf.WriteString(strings.Repeat(" ", 8-buf.Len())) |
|
} |
|
|
|
if r.runtextpos > 0 { |
|
buf.WriteString(syntax.CharDescription(r.runtext[r.runtextpos-1])) |
|
} else { |
|
buf.WriteRune('^') |
|
} |
|
|
|
buf.WriteRune('>') |
|
|
|
for i := r.runtextpos; i < r.runtextend; i++ { |
|
buf.WriteString(syntax.CharDescription(r.runtext[i])) |
|
} |
|
if buf.Len() >= 64 { |
|
buf.Truncate(61) |
|
buf.WriteString("...") |
|
} else { |
|
buf.WriteRune('$') |
|
} |
|
|
|
return buf.String() |
|
} |
|
|
|
// decide whether the pos |
|
// at the specified index is a boundary or not. It's just not worth |
|
// emitting inline code for this logic. |
|
func (r *runner) isBoundary(index, startpos, endpos int) bool { |
|
return (index > startpos && syntax.IsWordChar(r.runtext[index-1])) != |
|
(index < endpos && syntax.IsWordChar(r.runtext[index])) |
|
} |
|
|
|
func (r *runner) isECMABoundary(index, startpos, endpos int) bool { |
|
return (index > startpos && syntax.IsECMAWordChar(r.runtext[index-1])) != |
|
(index < endpos && syntax.IsECMAWordChar(r.runtext[index])) |
|
} |
|
|
|
// this seems like a comment to justify randomly picking 1000 :-P |
|
// We have determined this value in a series of experiments where x86 retail |
|
// builds (ono-lab-optimized) were run on different pattern/input pairs. Larger values |
|
// of TimeoutCheckFrequency did not tend to increase performance; smaller values |
|
// of TimeoutCheckFrequency tended to slow down the execution. |
|
const timeoutCheckFrequency int = 1000 |
|
|
|
func (r *runner) startTimeoutWatch() { |
|
if r.ignoreTimeout { |
|
return |
|
} |
|
|
|
r.timeoutChecksToSkip = timeoutCheckFrequency |
|
r.timeoutAt = time.Now().Add(r.timeout) |
|
} |
|
|
|
func (r *runner) checkTimeout() error { |
|
if r.ignoreTimeout { |
|
return nil |
|
} |
|
r.timeoutChecksToSkip-- |
|
if r.timeoutChecksToSkip != 0 { |
|
return nil |
|
} |
|
|
|
r.timeoutChecksToSkip = timeoutCheckFrequency |
|
return r.doCheckTimeout() |
|
} |
|
|
|
func (r *runner) doCheckTimeout() error { |
|
current := time.Now() |
|
|
|
if current.Before(r.timeoutAt) { |
|
return nil |
|
} |
|
|
|
if r.re.Debug() { |
|
//Debug.WriteLine("") |
|
//Debug.WriteLine("RegEx match timeout occurred!") |
|
//Debug.WriteLine("Specified timeout: " + TimeSpan.FromMilliseconds(_timeout).ToString()) |
|
//Debug.WriteLine("Timeout check frequency: " + TimeoutCheckFrequency) |
|
//Debug.WriteLine("Search pattern: " + _runregex._pattern) |
|
//Debug.WriteLine("Input: " + r.runtext) |
|
//Debug.WriteLine("About to throw RegexMatchTimeoutException.") |
|
} |
|
|
|
return fmt.Errorf("match timeout after %v on input `%v`", r.timeout, string(r.runtext)) |
|
} |
|
|
|
func (r *runner) initTrackCount() { |
|
r.runtrackcount = r.code.TrackCount |
|
} |
|
|
|
// getRunner returns a run to use for matching re. |
|
// It uses the re's runner cache if possible, to avoid |
|
// unnecessary allocation. |
|
func (re *Regexp) getRunner() *runner { |
|
re.muRun.Lock() |
|
if n := len(re.runner); n > 0 { |
|
z := re.runner[n-1] |
|
re.runner = re.runner[:n-1] |
|
re.muRun.Unlock() |
|
return z |
|
} |
|
re.muRun.Unlock() |
|
z := &runner{ |
|
re: re, |
|
code: re.code, |
|
} |
|
return z |
|
} |
|
|
|
// putRunner returns a runner to the re's cache. |
|
// There is no attempt to limit the size of the cache, so it will |
|
// grow to the maximum number of simultaneous matches |
|
// run using re. (The cache empties when re gets garbage collected.) |
|
func (re *Regexp) putRunner(r *runner) { |
|
re.muRun.Lock() |
|
re.runner = append(re.runner, r) |
|
re.muRun.Unlock() |
|
}
|
|
|