package syntaximport ()// Arbitrary number of repetitions of the same character when we'd prefer to represent that as a repeater of that character rather than a string.constMultiVsRepeaterLimit = 64typeRegexTreestruct { Root *RegexNode Caps map[int]int Capnumlist []int Captop int Capnames map[string]int Caplist []string Options RegexOptions FindOptimizations *FindOptimizations}// It is built into a parsed tree for a regular expression.// Implementation notes://// Since the node tree is a temporary data structure only used// during compilation of the regexp to integer codes, it's// designed for clarity and convenience rather than// space efficiency.//// RegexNodes are built into a tree, linked by the n.children list.// Each node also has a n.parent and n.ichild member indicating// its parent and which child # it is in its parent's list.//// RegexNodes come in as many types as there are constructs in// a regular expression, for example, "concatenate", "alternate",// "one", "rept", "group". There are also node types for basic// peephole optimizations, e.g., "onerep", "notsetrep", etc.//// Because perl 5 allows "lookback" groups that scan backwards,// each node also gets a "direction". Normally the value of// boolean n.backward = false.//// On the parse stack, each tree has a "role" - basically, the// nonterminal in the grammar that the parser has currently// assigned to the tree. That code is stored in n.role.//// Finally, some of the different kinds of nodes have data.// Two integers (for the looping constructs) are stored in// n.operands, an an object (either a string or a set)// is stored in n.datatypeRegexNodestruct { T NodeType Children []*RegexNode Str []rune Set *CharSet Ch rune M int N int Options RegexOptions Parent *RegexNode}typeNodeTypeint32const (// The following are leaves, and correspond to primitive operationsNtUnknownNodeType = -1//NtOnerep NodeType = 0 // lef,back char,min,max a {n} //NtNotonerep NodeType = 1 // lef,back char,min,max .{n} //NtSetrep NodeType = 2 // lef,back set,min,max [\d]{n}NtOneloopNodeType = 3// lef,back char,min,max a {,n}NtNotoneloopNodeType = 4// lef,back char,min,max .{,n}NtSetloopNodeType = 5// lef,back set,min,max [\d]{,n}NtOnelazyNodeType = 6// lef,back char,min,max a {,n}?NtNotonelazyNodeType = 7// lef,back char,min,max .{,n}?NtSetlazyNodeType = 8// lef,back set,min,max [\d]{,n}?NtOneNodeType = 9// lef char aNtNotoneNodeType = 10// lef char [^a]NtSetNodeType = 11// lef set [a-z\s] \w \s \dNtMultiNodeType = 12// lef string abcdNtRefNodeType = 13// lef group \#NtBolNodeType = 14// ^NtEolNodeType = 15// $NtBoundaryNodeType = 16// \bNtNonboundaryNodeType = 17// \BNtBeginningNodeType = 18// \ANtStartNodeType = 19// \GNtEndZNodeType = 20// \ZNtEndNodeType = 21// \Z// Interior nodes do not correspond to primitive operations, but // control structures compositing other operations// Concat and alternate take n children, and can run forward or backwardsNtNothingNodeType = 22// []NtEmptyNodeType = 23// ()NtAlternateNodeType = 24// a|bNtConcatenateNodeType = 25// abNtLoopNodeType = 26// m,x * + ? {,}NtLazyloopNodeType = 27// m,x *? +? ?? {,}?NtCaptureNodeType = 28// n ()NtGroupNodeType = 29// (?:)NtPosLookNodeType = 30// (?=) (?<=)NtNegLookNodeType = 31// (?!) (?<!)NtAtomicNodeType = 32// (?>) (?<)NtBackRefCondNodeType = 33// (?(n) | )NtExprCondNodeType = 34// (?(...) | )NtECMABoundaryNodeType = 41// \bNtNonECMABoundaryNodeType = 42// \B// Atomic loop of the specified character. // Operand 0 is the character. Operand 1 is the max iteration count.NtOneloopatomicNodeType = 43// Atomic loop of a single character other than the one specified. // Operand 0 is the character. Operand 1 is the max iteration count.NtNotoneloopatomicNodeType = 44// Atomic loop of a single character matching the specified set // Operand 0 is index into the strings table of the character class description. Operand 1 is the repetition count.NtSetloopatomicNodeType = 45// Updates the bumpalong position to the current position.NtUpdateBumpalongNodeType = 46// Matches one Unicode extended grapheme cluster (\X).NtGraphemeNodeType = 47)func newRegexNode( NodeType, RegexOptions) *RegexNode {return &RegexNode{T: ,Options: , }}func newRegexNodeCh( NodeType, RegexOptions, rune) *RegexNode {returnnodeWithCaseConversion(&RegexNode{T: ,Options: ,Ch: , })}func newRegexNodeStr( NodeType, RegexOptions, []rune) *RegexNode {return &RegexNode{T: ,Options: ,Str: , }}func newRegexNodeSet( NodeType, RegexOptions, *CharSet) *RegexNode {returnnodeWithCaseConversion(&RegexNode{T: ,Options: ,Set: , })}func newRegexNodeM( NodeType, RegexOptions, int) *RegexNode {return &RegexNode{T: ,Options: ,M: , }}func newRegexNodeMN( NodeType, RegexOptions, , int) *RegexNode {return &RegexNode{T: ,Options: ,M: ,N: , }}func nodeWithCaseConversion( *RegexNode) *RegexNode {// if opts are ignore case and our rune is impacted by casing // then we need to switch our type to the set version // NtOne = NtSetif .Options&IgnoreCase == 0 {return }if .Ch > 0 { := .Chif , := unicode.IsLower(), unicode.IsUpper(); || {/*var upper, lower rune // it's a capitalizable char if isUp { upper = ch lower = unicode.ToLower(ch) } else { lower = ch upper = unicode.ToUpper(ch) }*/ := &CharSet{} .addChar() .addCaseEquivalences() := NtSetswitch .T {caseNtOneloop, NtNotoneloop: = NtSetloopcaseNtOnelazy, NtNotonelazy: = NtSetlazy } .negate = .IsNotoneFamily()return &RegexNode{T: ,Options: .Options & ^IgnoreCase,Set: , } } } elseif .Set != nil {// just to be safe we don't modify the original set pointer // just in case it's used in a case-sensitive area := .Set.Copy() .addCaseEquivalences() .Set = & .Options &= ^IgnoreCase }return}func ( *RegexNode) () bool {return .T == NtSet || .T == NtSetloop || .T == NtSetlazy || .T == NtSetloopatomic}func ( *RegexNode) () bool {return .T == NtOne || .T == NtOneloop || .T == NtOnelazy || .T == NtOneloopatomic}func ( *RegexNode) () bool {return .T == NtNotone || .T == NtNotoneloop || .T == NtNotonelazy || .T == NtNotoneloopatomic}func ( *RegexNode) () bool {return .T == NtSetloop || .T == NtSetlazy || .T == NtSetloopatomic}func ( *RegexNode) () bool {return .T == NtOneloop || .T == NtOnelazy || .T == NtOneloopatomic}func ( *RegexNode) () bool {return .T == NtNotoneloop || .T == NtNotonelazy || .T == NtNotoneloopatomic}func ( *RegexNode) () bool {return .T == NtOneloopatomic || .T == NtNotoneloopatomic || .T == NtSetloopatomic}func ( *RegexNode) ( *bytes.Buffer) {for := 0; < len(.Str); ++ { .WriteRune(.Str[]) }}func ( *RegexNode) ( *RegexNode) { .Parent = := .reduce() .Parent = .Children = append(.Children, ) .Parent = }func ( *RegexNode) ( int, []*RegexNode) {for , := range { .Parent = } .Children = slices.Insert(.Children, , ...)//newChildren := make([]*RegexNode, 0, len(n.Children)+len(nodes)) //n.Children = append(append(append(newChildren, n.Children[:afterIndex]...), nodes...), n.Children[afterIndex:]...)}// removes children including the start but not the end indexfunc ( *RegexNode) (, int) { .Children = append(.Children[:], .Children[:]...)}func ( *RegexNode) ( int, *RegexNode) { .Parent = // so that the child can see its parent while being reduced = .reduce() .Parent = // in case Reduce returns a different node that needs to be reparented .Children[] = }// Pass type as OneLazy or OneLoopfunc ( *RegexNode) ( NodeType, , int) { .T += ( - NtOne) .M = .N = }// Performs additional optimizations on an entire tree prior to being used.//// Some optimizations are performed by the parser while parsing, and others are performed// as nodes are being added to the tree. The optimizations here expect the tree to be fully// formed, as they inspect relationships between nodes that may not have been in place as// individual nodes were being processed/added to the tree.func ( *RegexNode) () *RegexNode { := // Only apply optimization when LTR to avoid needing additional code for the much rarer RTL case. // Also only apply these optimizations when not using NonBacktracking, as these optimizations are // all about avoiding things that are impactful for the backtracking engines but nops for non-backtracking.if .Options&RightToLeft == 0 {// Optimization: eliminate backtracking for loops. // For any single-character loop (Oneloop, Notoneloop, Setloop), see if we can automatically convert // that into its atomic counterpart (Oneloopatomic, Notoneloopatomic, Setloopatomic) based on what // comes after it in the expression tree. .findAndMakeLoopsAtomic()// Optimization: backtracking removal at expression end. // If we find backtracking construct at the end of the regex, we can instead make it non-backtracking, // since nothing would ever backtrack into it anyway. Doing this then makes the construct available // to implementations that don't support backtracking. .eliminateEndingBacktracking()// Optimization: unnecessary re-processing of starting loops. // If an expression is guaranteed to begin with a single-character unbounded loop that isn't part of an alternation (in which case it // wouldn't be guaranteed to be at the beginning) or a capture (in which case a back reference could be influenced by its length), then we // can update the tree with a temporary node to indicate that the implementation should use that node's ending position in the input text // as the next starting position at which to start the next match. This avoids redoing matches we've already performed, e.g. matching // "\w+@dot.net" against "is this a valid address@dot.net", the \w+ will initially match the "is" and then will fail to match the "@". // Rather than bumping the scan loop by 1 and trying again to match at the "s", we can instead start at the " ". For functional correctness // we can only consider unbounded loops, as to be able to start at the end of the loop we need the loop to have consumed all possible matches; // otherwise, you could end up with a pattern like "a{1,3}b" matching against "aaaabc", which should match, but if we pre-emptively stop consuming // after the first three a's and re-start from that position, we'll end up failing the match even though it should have succeeded. We can also // apply this optimization to non-atomic loops: even though backtracking could be necessary, such backtracking would be handled within the processing // of a single starting position. Lazy loops similarly benefit, as a failed match will result in exploring the exact same search space as with // a greedy loop, just in the opposite order (and a successful match will overwrite the bumpalong position); we need to avoid atomic lazy loops, // however, as they will only end up as a repeater for the minimum length and thus will effectively end up with a non-infinite upper bound, which // we've already outlined is problematic. := .Children[0] // skip implicit root capture node := true// the root is implicitly atomic because nothing comes after it (same for the implicit root capture)for {if .T == NtAtomic { = .Children[0]continue } elseif .T == NtConcatenate { = false = .Children[0]continue } elseif .N == math.MaxInt32 && ((.T == NtOneloop || .T == NtOneloopatomic || .T == NtNotoneloop || .T == NtNotoneloopatomic || .T == NtSetloop || .T == NtSetloopatomic) || ((.T == NtOnelazy || .T == NtNotonelazy || .T == NtSetlazy) && !)) {if .Parent != nil && .Parent.T == NtConcatenate { .Parent.Children = slices.Insert(.Parent.Children, 1, &RegexNode{T: NtUpdateBumpalong, Options: .Options, Parent: .Parent}) } }break } }//debug helper //rootNode.ValidateFinalTreeInvariants()// Done optimizing. Return the final tree.return}// Finds {one/notone/set}loop nodes in the concatenation that can be automatically upgraded// to {one/notone/set}loopatomic nodes. Such changes avoid potential useless backtracking.// e.g. A*B (where sets A and B don't overlap) => (?>A*)B.func ( *RegexNode) () {if .Options&RightToLeft != 0 {// RTL is so rare, we don't need to spend additional time/code optimizing for it.return }// For all node types that have children, recur into each of those children.for := 0; < len(.Children); ++ { .Children[].() }// If this isn't a concatenation, nothing more to do.if .T != NtConcatenate {return }// This is a concatenation. Iterate through each pair of nodes in the concatenation seeing whether we can // make the first node (or its right-most child) atomic based on the second node (or its left-most child).for := 0; < len(.Children)-1; ++ { .Children[].processNode(.Children[+1]) }}func ( *RegexNode) ( *RegexNode) {// Skip down the node past irrelevant nodes.for {// We can always recur into captures and into the last node of concatenations.if .T == NtCapture || .T == NtConcatenate { = .Children[len(.Children)-1]continue }// For loops with at least one guaranteed iteration, we can recur into them, but // we need to be careful not to just always do so; the ending node of a loop can only // be made atomic if what comes after the loop but also the beginning of the loop are // compatible for the optimization.if .T == NtLoop { := .FindLastExpressionInLoopForAutoAtomic()if != nil { = continue } }// Can't skip any further.break }// If the node can be changed to atomic based on what comes after it, do so.switch .T {caseNtOneloop, NtNotoneloop, NtSetloop:if .canBeMadeAtomic(, true, false) {// The greedy loop doesn't overlap with what comes after it, which means giving anything it matches back will not // help the overall match to succeed, which means it can simply become atomic to match as much as possible. The call // to CanBeMadeAtomic passes iterateNullableSubsequent=true because, in a pattern like a*b*c*, when analyzing a*, we // want to examine the b* and the c* rather than just giving up after seeing that b* is nullable; in order to make // the a* atomic, we need to know that anything that could possibly come after the loop doesn't overlap. .makeLoopAtomic() }caseNtOnelazy, NtNotonelazy, NtSetlazy:if .canBeMadeAtomic(, false, true) {// The lazy loop doesn't overlap with what comes after it, which means it needs to match as much as its allowed // to match in order for there to be a possibility that what comes next matches (if it doesn't match as much // as it's allowed and there was still more it could match, then what comes next is guaranteed to not match, // since it doesn't match any of the same things the loop matches). We don't want to just make the lazy loop // atomic, as an atomic lazy loop matches as little as possible, not as much as possible. Instead, we want to // make the lazy loop into an atomic greedy loop. Note that when we check CanBeMadeAtomic, we need to set // "iterateNullableSubsequent" to false so that we only inspect non-nullable subsequent nodes. For example, // given a pattern like a*?b, we want to upgrade that loop to being greedy atomic, e.g. (?>a*)b. But given a // pattern like a*?b*, the subsequent node is nullable, which means it doesn't have to be part of a match, which // means the a*? could match by itself, in which case as it's lazy it needs to match as few a's as possible, e.g. // a+?b* against the input "aaaab" should match "a", not "aaaa" nor "aaaab". (Technically for lazy, we only need to prevent // walking off the end of the pattern, but it's not currently worth complicating the implementation for that case.) // allowLazy is set to true so that the implementation will analyze rather than ignore this node; generally lazy nodes // are ignored due to making them atomic not generally being a sound change, but here we're explicitly choosing to // given the circumstances. .T -= NtOnelazy - NtOneloop// lazy to greedy .makeLoopAtomic() }caseNtAlternate, NtBackRefCond, NtExprCond:// In the case of alternation, we can't change the alternation node itself // based on what comes after it (at least not with more complicated analysis // that factors in all branches together), but we can look at each individual // branch, and analyze ending loops in each branch individually to see if they // can be made atomic. Then if we do end up backtracking into the alternation, // we at least won't need to backtrack into that loop. The same is true for // conditionals, though we don't want to process the condition expression // itself, as it's already considered atomic and handled as part of ReduceExpressionConditional. := 0if .T == NtExprCond { = 1 }for ; < len(.Children); ++ { .Children[].() } }}func ( *RegexNode) () *RegexNode {// Remove IgnoreCase option from everything except a Backreferenceif .T != NtRef { .Options &= ^IgnoreCase }switch .T {caseNtAlternate:return .reduceAlternation()caseNtAtomic:return .reduceAtomic()caseNtConcatenate:return .reduceConcatenation()caseNtGroup:return .reduceGroup()caseNtLoop, NtLazyloop:return .reduceRep()caseNtPosLook, NtNegLook:return .reduceLookaround()caseNtSet, NtSetloop, NtSetlazy, NtSetloopatomic:return .reduceSet()caseNtExprCond:return .reduceExpressionConditional()caseNtBackRefCond:return .reduceBackreferenceConditional()default:return }}// / <summary>Optimizations for positive and negative lookaheads/behinds.</summary>func ( *RegexNode) () *RegexNode {// A lookaround is a zero-width atomic assertion. // As it's atomic, nothing will backtrack into it, and we can // eliminate any ending backtracking from it. .eliminateEndingBacktracking()// A positive lookaround wrapped around an empty is a nop, and we can reduce it // to simply Empty. A developer typically doesn't write this, but rather it evolves // due to optimizations resulting in empty.// A negative lookaround wrapped around an empty child, i.e. (?!), is // sometimes used as a way to insert a guaranteed no-match into the expression, // often as part of a conditional. We can reduce it to simply Nothing.if .Children[0].T == NtEmpty {if .T == NtPosLook { .T = NtEmpty } else { .T = NtNothing } .Children = nil }return}// Optimizations for backreference conditionals.func ( *RegexNode) () *RegexNode {// This isn't so much an optimization as it is changing the tree for consistency. We want // all engines to be able to trust that every backreference conditional will have two children, // even though it's optional in the syntax. If it's missing a "not matched" branch, // we add one that will match empty.iflen(.Children) == 1 { .addChild(&RegexNode{T: NtEmpty, Options: .Options}) }return}// / <summary>Optimizations for expression conditionals.</summary>func ( *RegexNode) () *RegexNode {// This isn't so much an optimization as it is changing the tree for consistency. We want // all engines to be able to trust that every expression conditional will have three children, // even though it's optional in the syntax. If it's missing a "not matched" branch, // we add one that will match empty.iflen(.Children) == 2 { .addChild(&RegexNode{T: NtEmpty, Options: .Options}) }// It's common for the condition to be an explicit positive lookahead, as specifying // that eliminates any ambiguity in syntax as to whether the expression is to be matched // as an expression or to be a reference to a capture group. After parsing, however, // there's no ambiguity, and we can remove an extra level of positive lookahead, as the // engines need to treat the condition as a zero-width positive, atomic assertion regardless. := .Children[0]if .T == NtPosLook && (.Options&RightToLeft) == 0 { .ReplaceChild(0, .Children[0]) }// We can also eliminate any ending backtracking in the condition, as the condition // is considered to be a positive lookahead, which is an atomic zero-width assertion. = .Children[0] .eliminateEndingBacktracking()return}// Remove unnecessary atomic nodes, and make appropriate descendents of the atomic node themselves atomic.// e.g. (?>(?>(?>a*))) => (?>a*)// e.g. (?>(abc*)*) => (?>(abc(?>c*))*)func ( *RegexNode) () *RegexNode { := := .Children[0]for .T == NtAtomic { = = .Children[0] }switch .T {// If the child is empty/nothing, there's nothing to be made atomic so the Atomic // node can simply be removed.caseNtEmpty, NtNothing:return// If the child is already atomic, we can just remove the atomic node.caseNtOneloopatomic, NtNotoneloopatomic, NtSetloopatomic:return// If an atomic subexpression contains only a {one/notone/set}{loop/lazy}, // change it to be an {one/notone/set}loopatomic and remove the atomic node.caseNtOneloop, NtNotoneloop, NtSetloop, NtOnelazy, NtNotonelazy, NtSetlazy: .makeLoopAtomic()return// Alternations have a variety of possible optimizations that can be applied // iff they're atomic.caseNtAlternate:if (.Options & RightToLeft) == 0 { := .Children// If an alternation is atomic and its first branch is Empty, the whole thing // is a nop, as Empty will match everything trivially, and no backtracking // into the node will be performed, making the remaining branches irrelevant.if [0].T == NtEmpty {return &RegexNode{T: NtEmpty, Options: .Options} }// Similarly, we can trim off any branches after an Empty, as they'll never be used. // An Empty will match anything, and thus branches after that would only be used // if we backtracked into it and advanced passed the Empty after trying the Empty... // but if the alternation is atomic, such backtracking won't happen.for := 1; < len()-1; ++ {if [].T == NtEmpty { = slices.Delete(, +1, len())break } }// If an alternation is atomic, we won't ever backtrack back into it, which // means order matters but not repetition. With backtracking, it would be incorrect // to convert an expression like "hi|there|hello" into "hi|hello|there", as doing // so could then change the order of results if we matched "hi" and then failed // based on what came after it, and both "hello" and "there" could be successful // with what came later. But without backtracking, we can reorder "hi|there|hello" // to instead be "hi|hello|there", as "hello" and "there" can't match the same text, // and once this atomic alternation has matched, we won't try another branch. This // reordering is valuable as it then enables further optimizations, e.g. // "hi|there|hello" => "hi|hello|there" => "h(?:i|ello)|there", which means we only // need to check the 'h' once in case it's not an 'h', and it's easier to employ different // code gen that, for example, switches on first character of the branches, enabling faster // choice of branch without always having to walk through each. := falsefor := 0; < len(); ++ {// Get the node that may start our range. If it's a one, multi, or concat of those, proceed. := []if .findBranchOneOrMultiStart() == nil {continue }// Find the contiguous range of nodes from this point that are similarly one, multi, or concat of those. := + 1for < len() && [].findBranchOneOrMultiStart() != nil { ++ }// If there's at least 3, there may be something to reorder (we won't reorder anything // before the starting position, and so only 2 items is considered ordered).if - >= 3 { := for < {// Get the starting character := [].findBranchOneOrMultiStart().FirstCharOfOneOrMulti()// Move compare to point to the last branch that has the same starting value.for < && [].findBranchOneOrMultiStart().FirstCharOfOneOrMulti() == { ++ }// Compare now points to the first node that doesn't match the starting node. // If we've walked off our range, there's nothing left to reorder.if < {// There may be something to reorder. See if there are any other nodes that begin with the same character.for := + 1; < ; ++ { := []if .findBranchOneOrMultiStart().FirstCharOfOneOrMulti() == { = slices.Delete(, , +1) = slices.Insert(, , ) ++ = true } } } } }// Move to the end of the range we've now explored. endExclusive is not a viable // starting position either, and the start++ for the loop will thus take us to // the next potential place to start a range. = } .Children = // If anything was reordered, there may be new optimization opportunities inside // of the alternation, so reduce it again.if { .ReplaceChild(0, ) = .Children[0] } }fallthrough// For everything else, try to reduce ending backtracking of the last contained expression.default: .eliminateEndingBacktracking()if .T == NtAlternate && .hasDisjointStartingSets() && .hasOnlyIntrinsicallyAtomicBranches() {return }return }}// hasDisjointStartingSets reports whether every branch is non-nullable and// begins with a character set that cannot overlap any other branch. In that// case, once one branch has matched, no sibling branch could match the same// starting position.func ( *RegexNode) () bool { , := .disjointStartingSets()return}// disjointStartingSets returns the non-nullable first-character set for each// branch when all of those sets are pairwise disjoint.func ( *RegexNode) () ([]*CharSet, bool) { := make([]*CharSet, 0, len(.Children))for , := range .Children {var *CharSetiftryFindFirstCharClass(, &) != 1 || == nil {returnnil, false }for , := range {if .MayOverlap() {returnnil, false } } = append(, ) }return , true}// disjointAtomicBranchSets returns the first-character sets when an// alternation can select a branch without leaving a backtracking choice.func ( *RegexNode) () ([]*CharSet, bool) {if .T != NtAlternate || !.hasOnlyIntrinsicallyAtomicBranches() {returnnil, false }return .disjointStartingSets()}// dispatchCandidates returns disjoint branch sets and the leading nodes that// can be folded into a consuming Dispatch instruction.func ( *RegexNode) () ([]*CharSet, []*RegexNode, []bool, bool) {iflen(.Children) >= 1<<16 {returnnil, nil, nil, false } , := .disjointAtomicBranchSets()if ! {returnnil, nil, nil, false } := make([]*RegexNode, len(.Children)) := make([]bool, len(.Children))for := range { := .Children[] := if .T == NtConcatenate && len(.Children) > 0 { = .Children[0] }switch .T {caseNtOne, NtNotone, NtSet, NtMulti: [] = default:returnnil, nil, nil, false } [] = == && (.T != NtMulti || len(.Str) == 1) }return , , , true}func ( *RegexNode) () bool {for , := range .Children {if !.isIntrinsicallyAtomic() {returnfalse } }returntrue}// isIntrinsicallyAtomic reports whether a successful match of this node has no// alternative input-consuming path to explore if something later fails.func ( *RegexNode) () bool {switch .T {caseNtOne, NtNotone, NtSet, NtMulti, NtRef, NtGrapheme,NtBol, NtEol, NtBoundary, NtNonboundary, NtECMABoundary, NtNonECMABoundary,NtBeginning, NtStart, NtEndZ, NtEnd, NtNothing, NtEmpty, NtUpdateBumpalong,NtOneloopatomic, NtNotoneloopatomic, NtSetloopatomic,NtPosLook, NtNegLook, NtAtomic:returntruecaseNtOneloop, NtNotoneloop, NtSetloop, NtOnelazy, NtNotonelazy, NtSetlazy:return .M == .NcaseNtCapture, NtGroup:returnlen(.Children) == 1 && .Children[0].()caseNtConcatenate:for , := range .Children {if !.() {returnfalse } }returntrue }returnfalse}func ( *RegexNode) () {switch .T {caseNtOneloop, NtNotoneloop, NtSetloop:// For loops, we simply change the Type to the atomic variant. // Atomic greedy loops should consume as many values as they can. .T += NtOneloopatomic - NtOneloopcaseNtOnelazy, NtNotonelazy, NtSetlazy:// For lazy, we not only change the Type, we also lower the max number of iterations // to the minimum number of iterations, creating a repeater, as they should end up // matching as little as possible. .T += NtOneloopatomic - NtOnelazy .N = .Mif .N == 0 {// If moving the max to be the same as the min dropped it to 0, there's no // work to be done for this node, and we can make it Empty. .T = NtEmpty .Str = nil .Ch = 0x0 } elseif .T == NtOneloopatomic && .N >= 2 && .N <= MultiVsRepeaterLimit {// If this is now a One repeater with a small enough length, // make it a Multi instead, as they're better optimized down the line. .T = NtMulti .Str = []rune(strings.Repeat(string(.Ch), .N)) .Ch = 0x0 .M = 0 .N = 0 } }}// Converts nodes at the end of the node tree to be atomic.// The correctness of this optimization depends on nothing being able to backtrack into// the provided node. That means it must be at the root of the overall expression, or// it must be an Atomic node that nothing will backtrack into by the very nature of Atomic.func ( *RegexNode) () {// Walk the tree starting from the current node. := for {switch .T {// {One/Notone/Set}loops can be upgraded to {One/Notone/Set}loopatomic nodes, e.g. [abc]* => (?>[abc]*). // And {One/Notone/Set}lazys can similarly be upgraded to be atomic, which really makes them into repeaters // or even empty nodes.caseNtOneloop, NtNotoneloop, NtSetloop, NtOnelazy, NtNotonelazy, NtSetlazy: .makeLoopAtomic()// Just because a particular node is atomic doesn't mean all its descendants are. // Process them as well. Lookarounds are implicitly atomic.caseNtAtomic, NtPosLook, NtNegLook: = .Children[0]continuecaseNtCapture, NtConcatenate:// For Capture and Concatenate, we just recur into their last child (only child in the case // of Capture). However, if the child is an alternation or loop, we can also make the // node itself atomic by wrapping it in an Atomic node. Since we later check to see whether a // node is atomic based on its parent or grandparent, we don't bother wrapping such a node in // an Atomic one if its grandparent is already Atomic. // e.g. [xyz](?:abc|def) => [xyz](?>abc|def)// validate grandparent isn't atomic := .Children[len(.Children)-1]if (.T == NtAlternate || .T == NtBackRefCond || .T == NtExprCond || .T == NtLoop || .T == NtLazyloop) && (.Parent == nil || .Parent.T != NtAtomic) { := &RegexNode{T: NtAtomic, Options: .Options} .addChild() .ReplaceChild(len(.Children)-1, ) } = continue// For alternate, we can recur into each branch separately. We use this iteration for the first branch. // Conditionals are just like alternations in this regard. // e.g. abc*|def* => ab(?>c*)|de(?>f*)caseNtAlternate, NtBackRefCond, NtExprCond: := len(.Children)for := 1; < ; ++ { .Children[].() }// ReduceExpressionConditional will have already applied ending backtracking removalif .T != NtExprCond { = .Children[0]continue }// For {Lazy}Loop, we search to see if there's a viable last expression, and iff there // is we recur into processing it. Also, as with the single-char lazy loops, LazyLoop // can have its max iteration count dropped to its min iteration count, as there's no // reason for it to match more than the minimal at the end; that in turn makes it a // repeater, which results in better code generation. // e.g. (?:abc*)* => (?:ab(?>c*))* // e.g. (abc*?)+? => (ab){1}caseNtLazyloop: .N = .MfallthroughcaseNtLoop:if .N == 1 {// If the loop has a max iteration count of 1 (e.g. it's an optional node), // there's no possibility for conflict between multiple iterations, so // we can process it. = .Children[0]continue } := .FindLastExpressionInLoopForAutoAtomic()if != nil { = continue// loop around to process node } }break }}// Recurs into the last expression of a loop node, looking to see if it can find a node// that could be made atomic _assuming_ the conditions exist for it with the loop's ancestors.// Returns The found node that should be explored further for auto-atomicity; null if it doesn't exist.func ( *RegexNode) () *RegexNode { := // Start by looking at the loop's sole child. = .Children[0]// Skip past captures.for .T == NtCapture { = .Children[0] }// If the loop's body is a concatenate, we can skip to its last child iff that // last child doesn't conflict with the first child, since this whole concatenation // could be repeated, such that the first node ends up following the last. For // example, in the expression (a+[def])*, the last child is [def] and the first is // a+, which can't possibly overlap with [def]. In contrast, if we had (a+[ade])*, // [ade] could potentially match the starting 'a'.if .T == NtConcatenate { := len(.Children) := .Children[-1]if .canBeMadeAtomic(.Children[0], false, false) {return } }// Otherwise, the loop has nothing that can participate in auto-atomicity.returnnil}// Determines whether a node can be switched to an atomic loop.//// The node following is subsequent, used to determine whether it overlaps.// iterateNullableSubsequent is whether to allow examining nodes beyond subsequent.// allowLazy is whether lazy loops in addition to greedy loops should be considered for atomicity.func ( *RegexNode) ( *RegexNode, , bool) bool {// In most case, we'll simply check the node against whatever subsequent is. However, in case // subsequent ends up being a loop with a min bound of 0, we'll also need to evaluate the node // against whatever comes after subsequent. In that case, we'll walk the tree to find the // next subsequent, and we'll loop around against to perform the comparison again.for {// Skip the successor down to the closest node that's guaranteed to follow it. := len(.Children)for ; > 0; = len(.Children) {if .T == NtConcatenate || .T == NtCapture || .T == NtAtomic || (.T == NtPosLook && .Options&RightToLeft == 0) || ((.T == NtLoop || .T == NtLazyloop) && .M > 0) { = .Children[0]continue }break }// If the current node's options don't match the subsequent node, then we cannot make it atomic. // This applies to RightToLeft for lookbehinds, as well as patterns that enable/disable global flags in the middle of the pattern.if .Options != .Options {returnfalse }// If the successor is an alternation, all of its children need to be evaluated, since any of them // could come after this node. If any of them fail the optimization, then the whole node fails. // This applies to expression conditionals as well, as long as they have both a yes and a no branch (if there's // only a yes branch, we'd need to also check whatever comes after the conditional). It doesn't apply to // backreference conditionals, as the condition itself is unknown statically and could overlap with the // loop being considered for atomicity.if .T == NtAlternate || (.T == NtExprCond && == 3) {// condition, yes, and no branchfor := 0; < ; ++ {if !.(.Children[], , false) {returnfalse } }returntrue }// If this node is a {one/notone/set}loop, see if it overlaps with its successor in the concatenation. // If it doesn't, then we can upgrade it to being a {one/notone/set}loopatomic. // Doing so avoids unnecessary backtracking.if .T == NtOneloop || (.T == NtOnelazy && ) {if (.T == NtOne && .Ch != .Ch) || (.T == NtNotone && .Ch == .Ch) || (.T == NtSet && !.Set.CharIn(.Ch)) || (.IsOneFamily() && .M > 0 && .Ch != .Ch) || (.IsNotoneFamily() && .M > 0 && .Ch == .Ch) || (.IsSetFamily() && .M > 0 && !.Set.CharIn(.Ch)) || (.T == NtMulti && .Ch != .Str[0]) || (.T == NtEnd) || (.T == NtEndZ && .Ch != '\n') || (.T == NtEol && .Ch != '\n') {returntrue }// The loop can be made atomic based on this subsequent node, but we'll need to evaluate the next one as well.if (.IsOneloopFamily() && .M == 0 && .Ch != .Ch) || (.IsNotoneloopFamily() && .M == 0 && .Ch == .Ch) || (.IsSetloopFamily() && .M == 0 && !.Set.CharIn(.Ch)) || (.T == NtBoundary && .M > 0 && IsWordChar(.Ch)) || (.T == NtNonboundary && .M > 0 && !IsWordChar(.Ch)) || (.T == NtECMABoundary && .M > 0 && IsECMAWordChar(.Ch)) || (.T == NtNonECMABoundary && .M > 0 && !IsECMAWordChar(.Ch)) {goto }returnfalse } elseif .T == NtNotoneloop || (.T == NtNotonelazy && ) {if (.T == NtOne && .Ch == .Ch) || (.IsOneFamily() && .M > 0 && .Ch == .Ch) || (.T == NtMulti && .Ch == .Str[0]) || (.T == NtEnd) {returntrue }// The loop can be made atomic based on this subsequent node, but we'll need to evaluate the next one as well.if .IsOneloopFamily() && .M == 0 && .Ch == .Ch {goto }returnfalse } elseif .T == NtSetloop || (.T == NtSetlazy && ) {if (.T == NtOne && !.Set.CharIn(.Ch)) || (.T == NtSet && !.Set.MayOverlap(.Set)) || (.IsOneloopFamily() && .M > 0 && !.Set.CharIn(.Ch)) || (.IsSetloopFamily() && .M > 0 && !.Set.MayOverlap(.Set)) || (.T == NtMulti && !.Set.CharIn(.Str[0])) || (.T == NtEnd) || (.T == NtEndZ && !.Set.CharIn('\n')) || (.T == NtEol && !.Set.CharIn('\n')) {returntrue }if (.IsOneloopFamily() && .M == 0 && !.Set.CharIn(.Ch)) || (.IsSetloopFamily() && .M == 0 && .Set.MayOverlap(.Set)) || (.T == NtBoundary && .M > 0 && (.Set.Equals(WordClass()) || .Set.Equals(DigitClass()))) || (.T == NtNonboundary && .M > 0 && (.Set.Equals(NotWordClass()) || .Set.Equals(NotDigitClass()))) || (.T == NtECMABoundary && .M > 0 && (.Set.Equals(ECMAWordClass()) || .Set.Equals(ECMADigitClass()))) || (.T == NtNonECMABoundary && .M > 0 && (.Set.Equals(NotECMAWordClass()) || .Set.Equals(NotDigitClass()))) {// The loop can be made atomic based on this subsequent node, but we'll need to evaluate the next one as well.goto }returnfalse } else {returnfalse } :// We only get here if the node could be made atomic based on subsequent but subsequent has a lower bound of zero // and thus we need to move subsequent to be the next node in sequence and loop around to try again.if ! {returnfalse }// To be conservative, we only walk up through a very limited set of constructs (even though we may have walked // down through more, like loops), looking for the next concatenation that we're not at the end of, at // which point subsequent becomes whatever node is next in that concatenation.for { := .Parentif == nil {// If we hit the root, we're at the end of the expression, at which point nothing could backtrack // in and we can declare success.returntrue }switch .T {caseNtAtomic, NtAlternate, NtCapture: = continuecaseNtConcatenate: := .Children := slices.Index(, )if +1 == len() { = continue } else { = [+1] }default:// Anything else, we don't know what to do, so we have to assume it could conflict with the loop.returnfalse }break } }}// Basic optimization. Single-letter alternations can be replaced// by faster set specifications, and nested alternations with no// intervening operators can be flattened://// a|b|c|def|g|h -> [a-c]|def|[gh]// apple|(?:orange|pear)|grape -> apple|orange|pear|grapefunc ( *RegexNode) () *RegexNode {iflen(.Children) == 0 {returnnewRegexNode(NtNothing, .Options) }iflen(.Children) == 1 {return .Children[0] } .reduceSingleLetterAndNestedAlternations() := .replaceNodeIfUnnecessary()if .T == NtAlternate { = .extractCommonPrefixText()if .T == NtAlternate { = .extractCommonPrefixOneNotoneSet()if .T == NtAlternate { = .removeRedundantEmptiesAndNothings() } } }return}// Analyzes all the branches of the alternation for text that's identical at the beginning// of every branch. That text is then pulled out into its own one or multi node in a// concatenation with the alternation (whose branches are updated to remove that prefix).// This is valuable for a few reasons. One, it exposes potentially more text to the// expression prefix analyzer used to influence FindFirstChar. Second, it exposes more// potential alternation optimizations, e.g. if the same prefix is followed in two branches// by sets that can be merged. Third, it reduces the amount of duplicated comparisons required// if we end up backtracking into subsequent branches.// e.g. abc|ade => a(?bc|de)func ( *RegexNode) () *RegexNode {// To keep things relatively simple, we currently only handle: // - Left to right (e.g. we don't process alternations in lookbehinds) // - Branches that are one or multi nodes, or that are concatenations beginning with one or multi nodes. // - All branches having the same options.// Only extract left-to-right prefixes.if (.Options & RightToLeft) != 0 {return }for := 0; < len(.Children)-1; ++ {// Process the first branch to get the maximum possible common string. := .Children[].findBranchOneOrMultiStart()if == nil {return } := .Options := .Strif .T == NtOne { = []rune{.Ch} }// Now compare the rest of the branches against it. := + 1for ; < len(.Children); ++ {// Get the starting node of the next branch. = .Children[].findBranchOneOrMultiStart()if == nil || .Options != {break }// See if the new branch's prefix has a shared prefix with the current one. // If it does, shorten to that; if it doesn't, bail.if .T == NtOne {if [0] != .Ch {break }iflen() != 1 { = [0:1] } } else { := len()iflen(.Str) < { = len(.Str) } := 0for < && [] == .Str[] { ++ }if == 0 {break } = [0:] } }// When we get here, we have a starting string prefix shared by all branches // in the range [startingIndex, endingIndex).if - <= 1 {// There's nothing to consolidate for this starting node.continue }// We should be able to consolidate something for the nodes in the range [startingIndex, endingIndex).// Create a new node of the form: // Concatenation(prefix, Alternation(each | node | with | prefix | removed)) // that replaces all these branches in this alternation.var *RegexNodeiflen() == 1 { = &RegexNode{T: NtOne, Options: , Ch: [0]} } else { = &RegexNode{T: NtMulti, Options: , Str: slices.Clone()} } := &RegexNode{T: NtAlternate, Options: }for := ; < ; ++ { := .Children[]if .T == NtConcatenate { .Children[0].processOneOrMulti() } else { .processOneOrMulti() } = .reduce() .addChild() }if .Parent != nil && .Parent.T == NtAtomic {var = &RegexNode{T: NtAtomic, Options: } .addChild() = } := &RegexNode{T: NtConcatenate, Options: } .addChild() .addChild() .ReplaceChild(, ) .Children = slices.Delete(.Children, +1, ) }iflen(.Children) == 1 {return .Children[0] }return}// This function optimizes out prefix nodes from alternation branches that are// the same across multiple contiguous branches.// e.g. \w12|\d34|\d56|\w78|\w90 => \w12|\d(?:34|56)|\w(?:78|90)func ( *RegexNode) () *RegexNode {// Only process left-to-right prefixes.if (.Options & RightToLeft) != 0 {return }// Only handle the case where each branch is a concatenationfor , := range .Children {if .T != NtConcatenate || len(.Children) < 2 {return } }for := 0; < len(.Children)-1; ++ {// Only handle the case where each branch begins with the same One, Notone, or Set (individual or loop). // Note that while we can do this for individual characters, fixed length loops, and atomic loops, doing // it for non-atomic variable length loops could change behavior as each branch could otherwise have a // different number of characters consumed by the loop based on what's after it. := .Children[].Children[0]if (!.IsOneFamily() && !.IsNotoneFamily() && !.IsSetFamily()) || .M != .N {// skip if it's not one of these scenarioscontinue }// Only handle the case where each branch begins with the exact same node value := + 1for ; < len(.Children); ++ { := .Children[].Children[0]if .T != .T || .Options != .Options || .M != .M || .N != .N || .Ch != .Ch || !slices.Equal(.Str, .Str) || !.Set.Equals(.Set) {break } }if - <= 1 {// Nothing to extract from this starting index.continue }// Remove the prefix node from every branch, adding it to a new alternation := &RegexNode{T: NtAlternate, Options: .Options}for := ; < ; ++ { .Children[].Children = slices.Delete(.Children[].Children, 0, 1) .addChild(.Children[]) }// If this alternation is wrapped as atomic, we need to do the same for the new alternation.if .Parent != nil && .Parent.T == NtAtomic { := &RegexNode{T: NtAtomic, Options: .Options} .addChild() = }// Now create a concatenation of the prefix node with the new alternation for the combined // branches, and replace all of the branches in this alternation with that new concatenation. := &RegexNode{T: NtConcatenate, Options: .Options} .addChild() .addChild() .ReplaceChild(, ) .Children = slices.Delete(.Children, +1, ) }return .replaceNodeIfUnnecessary()}// Removes unnecessary Empty and Nothing nodes from the alternation. A Nothing will never// match, so it can be removed entirely, and an Empty can be removed if there's a previous// Empty in the alternation: it's an extreme case of just having a repeated branch in an// alternation, and while we don't check for all duplicates, checking for empty is easy.func ( *RegexNode) () *RegexNode { := .Children , := 0, 0 := falsefor < len() { := []if .T == NtNothing || (.T == NtEmpty && ) { ++continue }if .T == NtEmpty { = true } [] = [] ++ ++ } .Children = slices.Delete(, , len())return .replaceNodeIfUnnecessary()}// Remove the starting text from the one or multi node. This may end up changing// the type of the node to be Empty if the starting text matches the node's full value.func ( *RegexNode) ( []rune) {if .T == NtOne { .T = NtEmpty .Ch = 0x0 } else {iflen(.Str) == len() { .T = NtEmpty .Str = nil } elseiflen(.Str)-1 == len() { .T = NtOne .Ch = .Str[len(.Str)-1] .Str = nil } else { .Str = .Str[len():] } }}// Finds the starting one or multi of the branch, if it has one; otherwise, returns null.// For simplicity, this only considers branches that are One or Multi, or a Concatenation// beginning with a One or Multi. We don't traverse more than one level to avoid the// complication of then having to later update that hierarchy when removing the prefix,// but it could be done in the future if proven beneficial enough.func ( *RegexNode) () *RegexNode { := if .T == NtConcatenate { = .Children[0] }if .T == NtOne || .T == NtMulti {return }returnnil}func ( *RegexNode) () { := false := falsevarRegexOptionsvar , intfor , = 0, 0; < len(.Children); , = +1, +1 { := .Children[]if < { .Children[] = }for {if .T == NtAlternate { .insertChildren(+1, .Children) -- } elseif .T == NtSet || .T == NtOne {// Cannot merge sets if L or I options differ, or if either are negated. := .Options & (RightToLeft | IgnoreCase)if .T == NtSet {if ! || != || || !.Set.IsMergeable() { = true = !.Set.IsMergeable() = break } } elseif ! || != || { = true = false = break }// The last node was a Set or a One, we're a Set or One and our options are the same. // Merge the two nodes. -- := .Children[]var *CharSetif .T == NtOne { = &CharSet{} .addChar(.Ch) } else { = .Set }if .T == NtOne { .addChar(.Ch) } else { .addSet(*.Set) } .T = NtSet .Set = if .Options&IgnoreCase != 0 { .Options &= ^IgnoreCase } } elseif .T == NtNothing { -- } else { = false = false }break } }if < { .removeChildren(, ) }}func ( *RegexNode) () *RegexNode {// Eliminate empties and concat adjacent strings/charsiflen(.Children) == 0 {returnnewRegexNode(NtEmpty, .Options) }// remove concatiflen(.Children) == 1 {return .Children[0] }// If any node in the concatenation is a Nothing, the concatenation itself is a Nothing.for := 0; < len(.Children); ++ { := .Children[]if .T == NtNothing {return } }// Coalesce adjacent loops. This helps to minimize work done by the interpreter, minimize code gen, // and also help to reduce catastrophic backtracking. .reduceConcatenationWithAdjacentLoops()// Coalesce adjacent characters/strings. This is done after the adjacent loop coalescing so that // a One adjacent to both a Multi and a Loop prefers being folded into the Loop rather than into // the Multi. Doing so helps with auto-atomicity when it's later applied. .reduceConcatenationWithAdjacentStrings()// If the concatenation is now empty, return an empty node, or if it's got a single child, return that child. // Otherwise, return this.return .replaceNodeIfUnnecessary()}func addMinLength(, int) int {const = math.MaxInt32 - 1if >= || >= || > - {return }return + }func multiplyMinLength(, int) int {const = math.MaxInt32 - 1if == 0 || == 0 {return0 }if >= || >= || > / {return }return * }func addMaxLength(, int) int {if < 0 || < 0 || >= math.MaxInt32 || >= math.MaxInt32 || > (math.MaxInt32-1)- {return -1 }return + }func multiplyMaxLength(, int) int {if < 0 || < 0 {return -1 }if == 0 || == 0 {return0 }if >= math.MaxInt32 || >= math.MaxInt32 || > (math.MaxInt32-1)/ {return -1 }return * }func maxLessThanTwiceMin(, int) bool {if <= math.MaxInt32/2 {return < *2 }return != math.MaxInt32}func canCombineCounts(, , , int) bool {// We shouldn't have an infinite minimum; bail if we find one. Also check for the // degenerate case where we'd make the min overflow or go infinite when it wasn't already.if == math.MaxInt32 || == math.MaxInt32 ||addMaxLength(, ) < 0 {returnfalse }// Similar overflow / go infinite check for max (which can be infinite).if != math.MaxInt32 && != math.MaxInt32 &&addMaxLength(, ) < 0 {returnfalse }returntrue}// Combine adjacent loops.// e.g. a*a*a* => a*// e.g. a+ab => a{2,}bfunc ( *RegexNode) () { , , := 0, 1, 1for < len(.Children) { := .Children[] := .Children[]if .Options == .Options {// Coalescing a loop with its same typeif ((.IsOneloopFamily() || .IsNotoneloopFamily()) && .T == .T && .Ch == .Ch) || (.IsSetloopFamily() && .T == .T && .Set.Equals(.Set)) {if .M > 0 && .IsAtomicloopFamily() {// Atomic loops can only be combined if the second loop has no lower bound, as if it has a lower bound, // combining them changes behavior. Uncombined, the first loop can consume all matching items; // the second loop might then not be able to meet its minimum and fail. But if they're combined, the combined // minimum of the sole loop could now be met, introducing matches where there shouldn't have been any.goto }if !canCombineCounts(.M, .N, .M, .N) {goto } .M += .Mif .N != math.MaxInt32 {if .N == math.MaxInt32 { .N = math.MaxInt32 } else { .N += .N } } ++continue } elseif ((.T == NtOneloop || .T == NtOnelazy) && .T == NtOne && .Ch == .Ch) || ((.T == NtNotoneloop || .T == NtNotonelazy) && .T == NtNotone && .Ch == .Ch) || ((.T == NtSetloop || .T == NtSetlazy) && .T == NtSet && .Set.Equals(.Set)) {// Coalescing a loop with an additional item of the same typeifcanCombineCounts(.M, .N, 1, 1) { .M++if .N != math.MaxInt32 { .N++ } ++continue } } elseif (.T == NtOneloop || .T == NtOnelazy) && .T == NtMulti && .Ch == .Str[0] {// Coalescing a loop with a subsequent string // Determine how many of the multi's characters can be combined. // We already checked for the first, so we know it's at least one. := 1for < len(.Str) && .Ch == .Str[] { ++ }ifcanCombineCounts(.M, .N, , ) {// Update the loop's bounds to include those characters from the multi .M += if .N != math.MaxInt32 { .N += }// If it was the full multi, skip/remove the multi and continue processing this loop.iflen(.Str) == { ++continue }// Otherwise, trim the characters from the multiple that were absorbed into the loop. // If it now only has a single character, it becomes a One.iflen(.Str)- == 1 { .T = NtOne .Ch = .Str[len(.Str)-1] .Str = nil } else { .Str = .Str[:] } }// NOTE: We could add support for coalescing a string with a subsequent loop, but the benefits of that // are limited. Pulling a subsequent string's prefix back into the loop helps with making the loop atomic, // but if the loop is after the string, pulling the suffix of the string forward into the loop may actually // be a deoptimization as those characters could end up matching more slowly as part of loop matching. } elseif (.T == NtOne && .IsOneloopFamily() && .Ch == .Ch) || (.T == NtNotone && .IsNotoneloopFamily() && .Ch == .Ch) || (.T == NtSet && .IsSetloopFamily() && .Set.Equals(.Set)) {// Coalescing an individual item with a loop.ifcanCombineCounts(1, 1, .M, .N) { .T = .T .M = .M + 1if .N == math.MaxInt32 { .N = math.MaxInt32 } else { .N = .N + 1 } ++continue } } elseif (.T == NtNotone && .T == NtNotone && .Ch == .Ch) || (.T == NtSet && .T == NtSet && .Set.Equals(.Set)) {// Coalescing an individual item with another individual item. // We don't coalesce adjacent One nodes into a Oneloop as we'd rather they be joined into a Multi. .makeRep(NtOneloop, 2, 2) ++continue } : } .Children[] = .Children[] ++ = ++ }if < len(.Children) { .Children = slices.Delete(.Children, , len(.Children)) }}// Basic optimization. Adjacent strings can be concatenated.//// (?:abc)(?:def) -> abcdeffunc ( *RegexNode) () {varRegexOptionsvarRegexOptionsvar , int := falsefor , = 0, 0; < len(.Children); , = +1, +1 {var , *RegexNode = .Children[]if < { .Children[] = }if .T == NtConcatenate && ((.Options & RightToLeft) == (.Options & RightToLeft)) {for := 0; < len(.Children); ++ { .Children[].Parent = }//insert at.children at i+1 index in n.children .insertChildren(+1, .Children) -- } elseif .T == NtMulti || .T == NtOne {// Cannot merge strings if L or I options differ = .Options & (RightToLeft | IgnoreCase)if ! || != { = true = continue } -- = .Children[]if .T == NtOne { .T = NtMulti .Str = []rune{.Ch} }if ( & RightToLeft) == 0 {if .T == NtOne { .Str = append(.Str, .Ch) } else { .Str = append(.Str, .Str...) } } else {if .T == NtOne {// insert at the front by expanding our slice, copying the data over, and then setting the value .Str = append(.Str, 0)copy(.Str[1:], .Str) .Str[0] = .Ch } else {//insert at the front...this one we'll make a new slice and copy both into it := make([]rune, len(.Str)+len(.Str))copy(, .Str)copy([len(.Str):], .Str) .Str = } } } elseif .T == NtEmpty { -- } else { = false } }if < {// remove indices j through i from the children .removeChildren(, ) }}// Nested repeaters just get multiplied with each other if they're not// too lumpyfunc ( *RegexNode) () *RegexNode {// Repeating a literal empty expression has no observable effect. In addition to // being unnecessary, retaining the loop would require one backtracking frame per // mandatory iteration even though no input is consumed.iflen(.Children) == 1 && .Children[0].T == NtEmpty {return .Children[0] } := := .T := .M := .Nforlen(.Children) > 0 { := .Children[0]// multiply reps of the same type onlyif .T != { := falseif == NtLoop {switch .T {caseNtOneloop, NtOneloopatomic, NtNotoneloop,NtNotoneloopatomic, NtSetloop, NtSetloopatomic: = true } } else {switch .T {caseNtOnelazy, NtNotonelazy, NtSetlazy: = true } }if ! {break } }// child can be too lumpy to blur, e.g., (a {100,105}) {3} or (a {2,})? // [but things like (a {2,})+ are not too lumpy...]if .M == 0 && .M > 1 || maxLessThanTwiceMin(.N, .M) {break } = if .M > 0 {if (math.MaxInt32-1)/.M < { .M = math.MaxInt32 } else { .M *= } }if .N > 0 {if (math.MaxInt32-1)/.N < { .N = math.MaxInt32 } else { .N *= } } }if == math.MaxInt32 {returnnewRegexNode(NtNothing, .Options) }// If the Loop or Lazyloop now only has one child node and its a Set, One, or Notone, // reduce to just Setloop/lazy, Oneloop/lazy, or Notoneloop/lazy. The parser will // generally have only produced the latter, but other reductions could have exposed // this.iflen(.Children) == 1 { := .Children[0]switch .T {caseNtOne, NtNotone, NtSet:if .T == NtLazyloop { .makeRep(NtOnelazy, .M, .N) } else { .makeRep(NtOneloop, .M, .N) } = } }return}// Simple optimization. If a concatenation or alternation has only// one child strip out the intermediate node. If it has zero children,// turn it into an empty.func ( *RegexNode) () *RegexNode {switchlen(.Children) {case0: := NtEmptyif .T == NtAlternate { = NtNothing }returnnewRegexNode(, .Options)case1:return .Children[0]default:return }}func ( *RegexNode) () *RegexNode { := for .T == NtGroup { = .Children[0] }return}// Simple optimization. If a set is a singleton, an inverse singleton,// or empty, it's transformed accordingly.func ( *RegexNode) () *RegexNode {// Extract empty-set, one and not-one case as specialif .Set == nil { .T = NtNothing } elseif .Set.IsSingleton() { .Ch = .Set.SingletonChar() .Set = nil .T += (NtOne - NtSet) } elseif .Set.IsSingletonInverse() { .Ch = .Set.SingletonChar() .Set = nil .T += (NtNotone - NtSet) }return}func ( *RegexNode) () *RegexNode {if .Options&RightToLeft != 0 && .T == NtConcatenate && len(.Children) > 0 {//reverse children orderfor , := 0, len(.Children)-1; < ; , = +1, -1 { .Children[], .Children[] = .Children[], .Children[] } }return}func ( *RegexNode) ( bool, , int) *RegexNode {// Certain cases of repeaters (min == max) can be handled speciallyif == 0 && == 0 {// The node is repeated 0 times, so it's actually empty.returnnewRegexNode(NtEmpty, .Options) }if == 1 && == 1 {// The node is repeated 1 time, so it's not actually a repeater.return }if == && <= MultiVsRepeaterLimit && .T == NtOne {// The same character is repeated a fixed number of times, so it's actually a multi. // While this could remain a repeater, multis are more readily optimized later in // processing. The counts used here in real-world expressions are invariably small (e.g. 4), // but we set an upper bound just to avoid creating really large strings. .T = NtMulti .Str = []rune(strings.Repeat(string(.Ch), )) .Ch = 0return }switch .T {caseNtOne, NtNotone, NtSet:if { .makeRep(NtOnelazy, , ) } else { .makeRep(NtOneloop, , ) }returndefault:varNodeTypeif { = NtLazyloop } else { = NtLoop } := newRegexNodeMN(, .Options, , ) .addChild()return }}// Computes a min bound on the required length of any string that could possibly match.// If the result is 0, there is no minimum we can enforce.func ( *RegexNode) () int {switch .T {caseNtOne, NtNotone, NtSet, NtGrapheme:// single charreturn1caseNtMulti:// Every character in the string needs to match.returnlen(.Str)caseNtNotonelazy, NtNotoneloop, NtNotoneloopatomic,NtOnelazy, NtOneloop, NtOneloopatomic, NtSetlazy, NtSetloop, NtSetloopatomic:// One character repeated at least M times.return .McaseNtLazyloop, NtLoop:// A node graph repeated at least M times.returnmultiplyMinLength(.M, .Children[0].())caseNtAlternate:// The minimum required length for any of the alternation's branches. := len(.Children) := .Children[0].()for := 1; < && > 0; ++ { := .Children[].()if < { = } }returncaseNtBackRefCond:// Minimum of its yes and no branches. The backreference doesn't add to the length. := .Children[0].()iflen(.Children) == 1 {return } := .Children[1].()if < {return }returncaseNtExprCond:// Minimum of its yes and no branches. The condition is a zero-width assertion.iflen(.Children) == 2 {return .Children[1].() } := .Children[1].() := .Children[2].()if < {return }returncaseNtConcatenate:// The sum of all of the concatenation's children. := 0for := 0; < len(.Children); ++ { = addMinLength(, .Children[].()) }returncaseNtAtomic, NtCapture, NtGroup:// For groups, we just delegate to the sole child.return .Children[0].()caseNtEmpty, NtNothing,NtBeginning, NtBol, NtBoundary, NtECMABoundary, NtEnd, NtEndZ, NtEol,NtNonboundary, NtNonECMABoundary, NtStart, NtNegLook, NtPosLook, NtRef:// Nothing to match. In the future, we could potentially use Nothing to say that the min length // is infinite, but that would require a different structure, as that would only apply if the // Nothing match is required in all cases (rather than, say, as one branch of an alternation). }return0}// Computes a maximum length of any string that could possibly match.// or -1 if the length may not always be the same.func ( *RegexNode) () int {switch .T {caseNtOne, NtNotone, NtSet:return1caseNtGrapheme:return -1caseNtMulti:returnlen(.Str)caseNtNotonelazy, NtNotoneloop, NtNotoneloopatomic,NtOnelazy, NtOneloop, NtOneloopatomic,NtSetlazy, NtSetloop, NtSetloopatomic:// Return the max number of iterations if there's an upper bound, or null if it's infiniteif .N == math.MaxInt32 {return -1 }return .NcaseNtLazyloop, NtLoop:if .N == math.MaxInt32 {return -1 }// A node graph repeated a fixed number of timesif := .Children[0].(); >= 0 {returnmultiplyMaxLength(.N, ) }caseNtAlternate:// The maximum length of any child branch, as long as they all have one. := .Children[0].()if < 0 {return -1 }for := 1; < len(.Children); ++ { := .Children[].()if < 0 {return -1 } = max(, ) }returncaseNtBackRefCond:// The maximum length of either child branch, as long as they both have one. := .Children[0].()if < 0 {return -1 } := .Children[1].()if < 0 {return -1 }returnmax(, )caseNtExprCond:// The condition for an expression conditional is a zero-width assertion. := .Children[1].()if < 0 {return -1 } := .Children[2].()if < 0 {return -1 }returnmax(, )caseNtConcatenate:// The sum of all of the concatenation's children's max lengths, as long as they all have one. := 0for := 0; < len(.Children); ++ { := .Children[].()if < 0 {return -1 } = addMaxLength(, )if < 0 {return -1 } }returncaseNtAtomic, NtCapture:// For groups, we just delegate to the sole child.return .Children[0].()caseNtEmpty, NtNothing, NtUpdateBumpalong,NtBeginning, NtBol, NtBoundary, NtECMABoundary, NtEnd, NtEndZ, NtEol,NtNonboundary, NtNonECMABoundary, NtStart, NtNegLook, NtPosLook://zero-widthreturn0caseNtRef:// Requires matching data available only at run-time. In the future, we could choose to find // and follow the capture group this aligns with, while being careful not to end up in an // infinite cycle.return -1 }return -1}// debug functionsvar typeStr = []string{"Onerep", "Notonerep", "Setrep","Oneloop", "Notoneloop", "Setloop","Onelazy", "Notonelazy", "Setlazy","One", "Notone", "Set","Multi", "Ref","Bol", "Eol", "Boundary", "Nonboundary","Beginning", "Start", "EndZ", "End","Nothing", "Empty","Alternate", "Concatenate","Loop", "Lazyloop","Capture", "Group", "PosLook", "NegLook", "Atomic","BackRefCond", "ExprCond","Unknown", "Unknown", "Unknown","Unknown", "Unknown", "Unknown","ECMABoundary", "NonECMABoundary","OneloopAtomic", "NotoneloopAtomic", "SetloopAtomic","UpdateBumpalong","Grapheme",}func ( *RegexNode) () string { := &bytes.Buffer{} .WriteString(typeStr[.T])if (.Options & ExplicitCapture) != 0 { .WriteString("-C") }if (.Options & IgnoreCase) != 0 { .WriteString("-I") }if (.Options & RightToLeft) != 0 { .WriteString("-L") }if (.Options & Multiline) != 0 { .WriteString("-M") }if (.Options & Singleline) != 0 { .WriteString("-S") }if (.Options & IgnorePatternWhitespace) != 0 { .WriteString("-X") }if (.Options & ECMAScript) != 0 { .WriteString("-E") }switch .T {caseNtOneloop, NtOneloopatomic, NtNotoneloop, NtOnelazy, NtNotonelazy, NtOne, NtNotone, NtNotoneloopatomic: .WriteString("(Ch = " + CharDescription(.Ch) + ")")caseNtCapture: .WriteString("(index = " + strconv.Itoa(.M) + ", unindex = " + strconv.Itoa(.N) + ")")caseNtRef, NtBackRefCond: .WriteString("(index = " + strconv.Itoa(.M) + ")")caseNtMulti:fmt.Fprintf(, "(String = %#v)", string(.Str))caseNtSet, NtSetloop, NtSetlazy, NtSetloopatomic: .WriteString("(Set = " + .Set.String() + ")") }switch .T {caseNtOneloop, NtNotoneloop, NtOnelazy, NtNotonelazy, NtSetloop, NtSetlazy, NtLoop, NtLazyloop,NtOneloopatomic, NtNotoneloopatomic, NtSetloopatomic: .WriteString("(Min = ") .WriteString(strconv.Itoa(.M)) .WriteString(", Max = ")if .N == math.MaxInt32 { .WriteString("inf") } else { .WriteString(strconv.Itoa(.N)) } .WriteString(")") }return .String()}var padSpace = []byte(" ")func ( *RegexTree) () string {return .Root.dump()}func ( *RegexNode) () string {var []int := := 0 := bytes.NewBufferString(.Description()) .WriteRune('\n')for {if .Children != nil && < len(.Children) { = append(, +1) = .Children[] = 0 := len()if > 32 { = 32 } .Write(padSpace[:]) .WriteString(.Description()) .WriteRune('\n') } else {iflen() == 0 {break } = [len()-1] = [:len()-1] = .Parent } }return .String()}// Determines whether the specified child index of a concatenation begins a sequence whose values// should be used to perform an ordinal case-insensitive comparison.//// When consumeZeroWidthNodes is false, the consumer needs the semantics of matching the produced string to fully represent// the semantics of all the consumed nodes, which means nodes can be consumed iff they produce text that's represented// by the resulting string. When true, the resulting string needs to fully represent all valid matches at that position,// but it can have false positives, which means the resulting string doesn't need to fully represent all zero-width nodes// consumed. true is only valid when used as part of a search to determine where to try a full match, not as part of// actual matching logic.// consumeZeroWidthNodes = falsefunc ( *RegexNode) ( int, int, bool) ( bool, int, string) { := &strings.Builder{}// We're looking in particular for sets of ASCII characters, so we focus only on sets with two characters in them, e.g. [Aa]. //twoChars := make([]rune, 0, 2)// Iterate from the child index to the exclusive upper bound.varintfor = ; < ; ++ { := .Children[]if .T == NtOne {// We only want to include ASCII characters, and only if they don't participate in case conversion // such that they only case to themselves and nothing other cases to them. Otherwise, including // them would potentially cause us to match against things not allowed by the pattern.if .Ch >= unicode.MaxASCII || participatesInCaseConversion(.Ch) {break } .WriteRune(.Ch) } elseif .T == NtMulti {// As with NtOne, the string needs to be composed solely of ASCII characters that // don't participate in case conversion. := slices.ContainsFunc(.Str, func( rune) bool { return > unicode.MaxASCII })if || anyParticipatesInCaseConversion(string(.Str)) {break } .WriteString(string(.Str)) } elseif .T == NtSet || ((.T == NtSetloop || .T == NtSetlazy || .T == NtSetloopatomic) && .M == .N) {// In particular we want to look for sets that contain only the upper and lowercase variant // of the same ASCII letter. , := .Set.containsAsciiIgnoreCaseCharacter()if ! {break } := .Mif .T == NtSet { = 1 } .WriteString(strings.Repeat(string([0]|0x20), )) } elseif .T == NtEmpty {// Skip over empty nodes, as they're pure nops. They would ideally have been optimized away, // but can still remain in some situations. } elseif &&// anchors (.T == NtBeginning || .T == NtBol || .T == NtStart ||// boundaries .T == NtBoundary || .T == NtECMABoundary || .T == NtNonboundary || .T == NtNonECMABoundary ||// lookarounds .T == NtNegLook || .T == NtPosLook ||// logic .T == NtUpdateBumpalong) {// Skip over zero-width nodes that might be reasonable at the beginning of or within a substring. // We can only do these if consumeZeroWidthNodes is true, as otherwise we'd be producing a string that // may not fully represent the semantics of this portion of the pattern. } else {break } }// If we found at least two characters, consider it a sequence found. It's possible // they all came from the same node, so this could be a sequence of just one node.if .Len() >= 2 {returntrue, - , .String() }// No sequence found.returnfalse, 0, ""}func ( *RegexNode) () bool {if .T == NtOne || .T == NtNotone || .T == NtSet || .T == NtMulti {returntrue }if (.IsSetloopFamily() || .IsNotoneloopFamily() || .IsOneloopFamily()) && .M == .N {returntrue }returnfalse}// Determine whether the specified child node is the beginning of a sequence that can// trivially have length checks combined in order to avoid bounds checks.// requiredLength is The sum of all the fixed lengths for the nodes in the sequence.</param>// exclusiveEnd is The index of the node just after the last one in the sequence.</param>// returns true if more than one node can have their length checks combined; otherwise, false.</returns>//// There are additional node types for which we can prove a fixed length, e.g. examining all branches// of an alternation and returning true if all their lengths are equal. However, the primary purpose// of this method is to avoid bounds checks by consolidating length checks that guard accesses to// strings/spans for which the JIT can see a fixed index within bounds, and alternations employ// patterns that defeat that (e.g. reassigning the span in question). As such, the implementation// remains focused on only a core subset of nodes that are a) likely to be used in concatenations and// b) employ simple patterns of checks.func ( *RegexNode) ( int, *int, *int) bool { := .Children[]if .canJoinLengthCheck() { * = .ComputeMinLength()for * = + 1; * < len(.Children); *++ { = .Children[*]if !.canJoinLengthCheck() {break } * += .ComputeMinLength() }if *- > 1 {returntrue } } * = 0 * = 0returnfalse}typeStartingLiteralstruct { Range SingleRange String []rune SetChars []rune Negated bool}func ( *RegexNode) () *StartingLiteral { := .FindStartingLiteralNode(true)if == nil {returnnil }if .IsOneFamily() {return &StartingLiteral{Range: SingleRange{.Ch, .Ch}, Negated: false} }if .IsNotoneFamily() {return &StartingLiteral{Range: SingleRange{.Ch, .Ch}, Negated: true} }if .IsSetFamily() { := .Set.GetIfNRanges(1)iflen() == 1 && [0].Last-[0].First > 1 {return &StartingLiteral{Range: [0], Negated: .Set.IsNegated()} } := .Set.GetSetChars(128)iflen() > 0 {return &StartingLiteral{SetChars: , Negated: .Set.IsNegated()} } }if .T == NtMulti {return &StartingLiteral{String: .Str} }returnnil}// Finds the guaranteed beginning literal(s) of the node, or null if none exists.// allowZeroWidth = truefunc ( *RegexNode) ( bool) *RegexNode { := for {if != nil && .Options&RightToLeft == 0 {switch .T {caseNtOne, NtNotone, NtMulti, NtSet:returncaseNtOneloop, NtOneloopatomic, NtOnelazy,NtNotoneloop, NtNotoneloopatomic, NtNotonelazy,NtSetloop, NtSetloopatomic, NtSetlazy:if .M > 0 {return }caseNtAtomic, NtConcatenate, NtCapture, NtGroup: = .Children[0]continuecaseNtLoop, NtLazyloop: = .Children[0]continuecaseNtPosLook:if { = .Children[0]continue } } }returnnil }}// Gets the character that begins a One or Multi.func ( *RegexNode) () rune {if .IsOneFamily() {return .Ch }return .Str[0]}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.