package syntax
import (
"bytes"
"cmp"
"fmt"
"slices"
)
type FindOptimizations struct {
rightToLeft bool
asciiLookups [][]uint
FindMode FindNextStartingPositionMode
LeadingAnchor NodeType
TrailingAnchor NodeType
MinRequiredLength int
MaxPossibleLength int
LeadingPrefix string
LeadingPrefixes []string
LeadingPrefixesRunes [][]rune
FixedDistanceLiteral FixedDistanceLiteral
FixedDistanceSets []FixedDistanceSet
LiteralAfterLoop *LiteralAfterLoop
LandmarkChain *RequiredLandmarkChain
LeadingPrefixFirstRunes []rune
}
type LiteralAfterLoop struct {
String string
StringIgnoreCase bool
Char rune
Chars []rune
LoopNode *RegexNode
}
type FixedDistanceSet struct {
Set *CharSet
Chars []rune
Negated bool
Range *SingleRange
Distance int
}
type FixedDistanceLiteral struct {
S string
C rune
Distance int
}
type RequiredLandmarkChain struct {
LeadingLoopSet *CharSet
Landmarks []RequiredLandmark
}
type RequiredLandmark struct {
Alternatives []RequiredLandmarkAlternative
}
type RequiredLandmarkAlternative struct {
Literal []rune
Set *CharSet
LeadingWhitespaceSet *CharSet
TrailingWhitespaceSet *CharSet
MinRepeat int
MaxRepeat int
RequireWhitespaceBefore bool
RequireWhitespaceAfter bool
}
type FindNextStartingPositionMode int
const (
NoSearch FindNextStartingPositionMode = iota
LeadingAnchor_LeftToRight_Beginning
LeadingAnchor_LeftToRight_Start
LeadingAnchor_LeftToRight_EndZ
LeadingAnchor_LeftToRight_End
LeadingAnchor_RightToLeft_Beginning
LeadingAnchor_RightToLeft_Start
LeadingAnchor_RightToLeft_EndZ
LeadingAnchor_RightToLeft_End
TrailingAnchor_FixedLength_LeftToRight_End
TrailingAnchor_FixedLength_LeftToRight_EndZ
LeadingString_LeftToRight
LeadingString_RightToLeft
LeadingString_OrdinalIgnoreCase_LeftToRight
LeadingStrings_LeftToRight
LeadingStrings_OrdinalIgnoreCase_LeftToRight
LeadingSet_LeftToRight
LeadingSet_RightToLeft
LeadingChar_RightToLeft
FixedDistanceChar_LeftToRight
FixedDistanceString_LeftToRight
FixedDistanceSets_LeftToRight
LiteralAfterLoop_LeftToRight
RequiredLandmarkChain_LeftToRight
)
func (m FindNextStartingPositionMode ) String () string {
switch m {
case NoSearch :
return "NoSearch"
case LeadingAnchor_LeftToRight_Beginning :
return "LeadingAnchor_LeftToRight_Beginning"
case LeadingAnchor_LeftToRight_Start :
return "LeadingAnchor_LeftToRight_Start"
case LeadingAnchor_LeftToRight_EndZ :
return "LeadingAnchor_LeftToRight_EndZ"
case LeadingAnchor_LeftToRight_End :
return "LeadingAnchor_LeftToRight_End"
case LeadingAnchor_RightToLeft_Beginning :
return "LeadingAnchor_RightToLeft_Beginning"
case LeadingAnchor_RightToLeft_Start :
return "LeadingAnchor_RightToLeft_Start"
case LeadingAnchor_RightToLeft_EndZ :
return "LeadingAnchor_RightToLeft_EndZ"
case LeadingAnchor_RightToLeft_End :
return "LeadingAnchor_RightToLeft_End"
case TrailingAnchor_FixedLength_LeftToRight_End :
return "TrailingAnchor_FixedLength_LeftToRight_End"
case TrailingAnchor_FixedLength_LeftToRight_EndZ :
return "TrailingAnchor_FixedLength_LeftToRight_EndZ"
case LeadingString_LeftToRight :
return "LeadingString_LeftToRight"
case LeadingString_RightToLeft :
return "LeadingString_RightToLeft"
case LeadingString_OrdinalIgnoreCase_LeftToRight :
return "LeadingString_OrdinalIgnoreCase_LeftToRight"
case LeadingStrings_LeftToRight :
return "LeadingStrings_LeftToRight"
case LeadingStrings_OrdinalIgnoreCase_LeftToRight :
return "LeadingStrings_OrdinalIgnoreCase_LeftToRight"
case LeadingSet_LeftToRight :
return "LeadingSet_LeftToRight"
case LeadingSet_RightToLeft :
return "LeadingSet_RightToLeft"
case LeadingChar_RightToLeft :
return "LeadingChar_RightToLeft"
case FixedDistanceChar_LeftToRight :
return "FixedDistanceChar_LeftToRight"
case FixedDistanceString_LeftToRight :
return "FixedDistanceString_LeftToRight"
case FixedDistanceSets_LeftToRight :
return "FixedDistanceSets_LeftToRight"
case LiteralAfterLoop_LeftToRight :
return "LiteralAfterLoop_LeftToRight"
case RequiredLandmarkChain_LeftToRight :
return "RequiredLandmarkChain_LeftToRight"
default :
return fmt .Sprintf ("FindNextStartingPositionMode(%d)" , int (m ))
}
}
func (f *FindOptimizations ) Dump () string {
buf := &bytes .Buffer {}
if f == nil {
fmt .Fprintln (buf , "Find mode: n/a" )
return buf .String ()
}
fmt .Fprintf (buf , "Find mode: %s\n" , f .FindMode )
fmt .Fprintf (buf , "Min length: %d\n" , f .MinRequiredLength )
if f .MaxPossibleLength > 0 {
fmt .Fprintf (buf , "Max length: %d\n" , f .MaxPossibleLength )
}
switch f .FindMode {
case LeadingAnchor_LeftToRight_Beginning , LeadingAnchor_LeftToRight_Start ,
LeadingAnchor_LeftToRight_EndZ , LeadingAnchor_LeftToRight_End ,
LeadingAnchor_RightToLeft_Beginning , LeadingAnchor_RightToLeft_Start ,
LeadingAnchor_RightToLeft_EndZ , LeadingAnchor_RightToLeft_End :
fmt .Fprintf (buf , "Anchor: %v\n" , f .LeadingAnchor )
case TrailingAnchor_FixedLength_LeftToRight_End , TrailingAnchor_FixedLength_LeftToRight_EndZ :
fmt .Fprintf (buf , "Anchor: %v\n" , f .TrailingAnchor )
case LeadingString_LeftToRight , LeadingString_RightToLeft , LeadingString_OrdinalIgnoreCase_LeftToRight :
fmt .Fprintf (buf , "Prefix: %q\n" , f .LeadingPrefix )
case LeadingStrings_LeftToRight , LeadingStrings_OrdinalIgnoreCase_LeftToRight :
fmt .Fprintf (buf , "Prefixes: %q\n" , f .LeadingPrefixes )
case FixedDistanceChar_LeftToRight :
fmt .Fprintf (buf , "Literal: %q at distance %d\n" , f .FixedDistanceLiteral .C , f .FixedDistanceLiteral .Distance )
case FixedDistanceString_LeftToRight :
fmt .Fprintf (buf , "Literal: %q at distance %d\n" , f .FixedDistanceLiteral .S , f .FixedDistanceLiteral .Distance )
case FixedDistanceSets_LeftToRight :
for i , set := range f .FixedDistanceSets {
fmt .Fprintf (buf , "Set[%d]: distance=%d %s\n" , i , set .Distance , fixedDistanceSetDescription (set ))
}
case LiteralAfterLoop_LeftToRight :
fmt .Fprintf (buf , "Literal: %s\n" , literalAfterLoopDescription (f .LiteralAfterLoop ))
case RequiredLandmarkChain_LeftToRight :
fmt .Fprintf (buf , "Landmarks: %s\n" , landmarkChainDescription (f .LandmarkChain ))
}
return buf .String ()
}
func fixedDistanceSetDescription(set FixedDistanceSet ) string {
switch {
case len (set .Chars ) > 0 :
return fmt .Sprintf ("chars=%q negated=%t" , string (set .Chars ), set .Negated )
case set .Range != nil :
return fmt .Sprintf ("range=%q-%q negated=%t" , set .Range .First , set .Range .Last , set .Negated )
case set .Set != nil :
return fmt .Sprintf ("set=%s" , set .Set .String ())
default :
return "set=<nil>"
}
}
func literalAfterLoopDescription(literal *LiteralAfterLoop ) string {
if literal == nil {
return "<nil>"
}
loopSet := "<nil>"
if literal .LoopNode != nil && literal .LoopNode .Set != nil {
loopSet = literal .LoopNode .Set .String ()
}
switch {
case literal .String != "" :
return fmt .Sprintf ("string=%q ignoreCase=%t after loop set %s" , literal .String , literal .StringIgnoreCase , loopSet )
case len (literal .Chars ) > 0 :
return fmt .Sprintf ("chars=%q after loop set %s" , string (literal .Chars ), loopSet )
default :
return fmt .Sprintf ("char=%q after loop set %s" , literal .Char , loopSet )
}
}
func landmarkChainDescription(chain *RequiredLandmarkChain ) string {
if chain == nil {
return "<nil>"
}
loopSet := "<nil>"
if chain .LeadingLoopSet != nil {
loopSet = chain .LeadingLoopSet .String ()
}
buf := &bytes .Buffer {}
fmt .Fprintf (buf , "leadingLoop=%s" , loopSet )
for i , landmark := range chain .Landmarks {
fmt .Fprintf (buf , " landmark[%d]=" , i )
for j , alt := range landmark .Alternatives {
if j > 0 {
buf .WriteString ("|" )
}
buf .WriteString (requiredLandmarkAlternativeDescription (alt ))
}
}
return buf .String ()
}
func requiredLandmarkAlternativeDescription(alt RequiredLandmarkAlternative ) string {
core := ""
switch {
case len (alt .Literal ) > 0 :
core = fmt .Sprintf ("literal=%q" , string (alt .Literal ))
case alt .Set != nil :
core = fmt .Sprintf ("set=%s repeat=%d..%d" , alt .Set .String (), alt .MinRepeat , alt .MaxRepeat )
default :
core = "<empty>"
}
if alt .LeadingWhitespaceSet != nil {
core += fmt .Sprintf (" leadingWhitespace=%s required=%t" , alt .LeadingWhitespaceSet .String (), alt .RequireWhitespaceBefore )
}
if alt .TrailingWhitespaceSet != nil {
core += fmt .Sprintf (" trailingWhitespace=%s required=%t" , alt .TrailingWhitespaceSet .String (), alt .RequireWhitespaceAfter )
}
return core
}
func newFindOptimizations(tree *RegexTree , opt ParseOptions ) *FindOptimizations {
f := newFindOptimizationsForNode (tree .Root , opt , false )
if !f .rightToLeft && !f .isUseful () {
if positiveLookahead , _ := findLeadingPositiveLookahead (tree .Root ); positiveLookahead != nil {
positiveLookaheadOpts := newFindOptimizationsForNode (positiveLookahead .Children [0 ], opt , true )
positiveLookaheadOpts .MinRequiredLength = max (f .MinRequiredLength , positiveLookaheadOpts .MinRequiredLength )
positiveLookaheadOpts .MaxPossibleLength = f .MaxPossibleLength
f = positiveLookaheadOpts
}
}
return f
}
func newFindOptimizationsForNode(root *RegexNode , opt ParseOptions , isLeadingPartial bool ) *FindOptimizations {
f := &FindOptimizations {
rightToLeft : opt .RegexOptions &RightToLeft != 0 ,
MinRequiredLength : root .ComputeMinLength (),
LeadingAnchor : findLeadingOrTrailingAnchor (root , true ),
MaxPossibleLength : -1 ,
}
if f .rightToLeft && f .LeadingAnchor == NtBol {
f .LeadingAnchor = NtUnknown
}
f .FindMode = getFindMode (f .rightToLeft , f .LeadingAnchor )
if f .FindMode != NoSearch {
return f
}
if !f .rightToLeft && !isLeadingPartial {
f .TrailingAnchor = findLeadingOrTrailingAnchor (root , false )
if f .TrailingAnchor == NtEnd || f .TrailingAnchor == NtEndZ {
f .MaxPossibleLength = root .computeMaxLength ()
if f .MinRequiredLength == f .MaxPossibleLength {
if f .TrailingAnchor == NtEnd {
f .FindMode = TrailingAnchor_FixedLength_LeftToRight_End
} else {
f .FindMode = TrailingAnchor_FixedLength_LeftToRight_EndZ
}
return f
}
}
}
prefix := findPrefix (root )
if len (prefix ) > 1 {
f .LeadingPrefix = prefix
if f .rightToLeft {
f .FindMode = LeadingString_RightToLeft
} else {
f .FindMode = LeadingString_LeftToRight
}
return f
}
dfa := false
codeGen := opt .CodeGen && !dfa
interpreter := !codeGen && !dfa
if f .rightToLeft {
set := findFirstCharClass (root )
if set != nil {
var chars []rune
if !set .IsNegated () {
chars = set .GetSetChars (5 )
}
if !codeGen && len (chars ) == 1 {
f .FixedDistanceLiteral .C = chars [0 ]
f .FindMode = LeadingChar_RightToLeft
} else {
f .FixedDistanceSets = []FixedDistanceSet {{
Chars : chars ,
Set : set ,
Distance : 0 ,
}}
f .FindMode = LeadingSet_RightToLeft
f .asciiLookups = make ([][]uint , 1 )
}
}
return f
}
prefix = findPrefixOrdinalCaseInsensitive (root )
if len (prefix ) > 1 {
f .LeadingPrefix = prefix
f .FindMode = LeadingString_OrdinalIgnoreCase_LeftToRight
return f
}
if !interpreter {
ciPrefixes := findPrefixes (root , true )
if len (ciPrefixes ) > 1 {
f .LeadingPrefixes = ciPrefixes
f .LeadingPrefixesRunes = toRunePrefixes (ciPrefixes )
f .LeadingPrefixFirstRunes = leadingPrefixFirstRunes (f .LeadingPrefixesRunes )
f .FindMode = LeadingStrings_OrdinalIgnoreCase_LeftToRight
return f
}
}
fixedDistanceSets := findFixedDistanceSets (root , !interpreter )
if len (fixedDistanceSets ) > 0 {
bestFixedDistanceString := findFixedDistanceString (fixedDistanceSets )
if bestFixedDistanceString != nil {
f .FindMode = FixedDistanceString_LeftToRight
f .FixedDistanceLiteral = *bestFixedDistanceString
return f
}
}
if landmarkChain := findRequiredLandmarkChain (root ); landmarkChain != nil {
f .FindMode = RequiredLandmarkChain_LeftToRight
f .LandmarkChain = landmarkChain
return f
}
literalAfterLoop := findLiteralFollowingLeadingLoop (root )
if len (fixedDistanceSets ) > 0 {
slices .SortFunc (fixedDistanceSets , compareFixedDistanceSetsByQuality )
if !interpreter && !mayContainCaseInsensitiveMatching (root ) && hasHighFrequencyChars (fixedDistanceSets [0 ]) {
caseSensitivePrefixes := findPrefixes (root , false )
if len (caseSensitivePrefixes ) > 1 {
f .LeadingPrefixes = caseSensitivePrefixes
f .LeadingPrefixesRunes = toRunePrefixes (caseSensitivePrefixes )
f .LeadingPrefixFirstRunes = leadingPrefixFirstRunes (f .LeadingPrefixesRunes )
f .FindMode = LeadingStrings_LeftToRight
return f
}
}
if literalAfterLoop == nil || (len (fixedDistanceSets [0 ].Chars ) > 0 && !fixedDistanceSets [0 ].Negated ) {
if !codeGen && len (fixedDistanceSets ) == 1 && len (fixedDistanceSets [0 ].Chars ) == 1 &&
!fixedDistanceSets [0 ].Negated {
f .FixedDistanceLiteral = FixedDistanceLiteral {
C : fixedDistanceSets [0 ].Chars [0 ],
Distance : fixedDistanceSets [0 ].Distance ,
}
f .FindMode = FixedDistanceChar_LeftToRight
} else {
const MaxSetsToUse = 3
if len (fixedDistanceSets ) > MaxSetsToUse {
fixedDistanceSets = fixedDistanceSets [:MaxSetsToUse ]
}
f .FixedDistanceSets = fixedDistanceSets
if len (fixedDistanceSets ) == 1 && fixedDistanceSets [0 ].Distance == 0 {
f .FindMode = LeadingSet_LeftToRight
} else {
f .FindMode = FixedDistanceSets_LeftToRight
}
f .asciiLookups = make ([][]uint , len (fixedDistanceSets ))
}
return f
}
}
if literalAfterLoop != nil {
f .FindMode = LiteralAfterLoop_LeftToRight
f .LiteralAfterLoop = literalAfterLoop
f .asciiLookups = make ([][]uint , 1 )
}
return f
}
func (f *FindOptimizations ) isUseful () bool {
return f .FindMode != NoSearch || f .LeadingAnchor == NtBol
}
func toRunePrefixes(prefixes []string ) [][]rune {
if len (prefixes ) == 0 {
return nil
}
runes := make ([][]rune , len (prefixes ))
for i , prefix := range prefixes {
runes [i ] = []rune (prefix )
}
return runes
}
func leadingPrefixFirstRunes(prefixes [][]rune ) []rune {
first := make ([]rune , 0 , len (prefixes ))
for _ , prefix := range prefixes {
if len (prefix ) > 0 && !slices .Contains (first , prefix [0 ]) {
first = append (first , prefix [0 ])
}
}
return first
}
func getFindMode(rtl bool , t NodeType ) FindNextStartingPositionMode {
if rtl {
switch t {
case NtBeginning :
return LeadingAnchor_RightToLeft_Beginning
case NtStart :
return LeadingAnchor_RightToLeft_Start
case NtEnd :
return LeadingAnchor_RightToLeft_End
case NtEndZ :
return LeadingAnchor_RightToLeft_EndZ
}
} else {
switch t {
case NtBeginning :
return LeadingAnchor_LeftToRight_Beginning
case NtStart :
return LeadingAnchor_LeftToRight_Start
case NtEnd :
return LeadingAnchor_LeftToRight_End
case NtEndZ :
return LeadingAnchor_LeftToRight_EndZ
}
}
return NoSearch
}
func findFixedDistanceString(fixedDistanceSets []FixedDistanceSet ) *FixedDistanceLiteral {
var best *FixedDistanceLiteral
if len (fixedDistanceSets ) >= 2 {
slices .SortFunc (fixedDistanceSets , func (s1 , s2 FixedDistanceSet ) int {
return cmp .Compare (s1 .Distance , s2 .Distance )
})
vsb := &bytes .Buffer {}
start := -1
for i := 0 ; i < len (fixedDistanceSets )+1 ; i ++ {
chars := []rune (nil )
if i < len (fixedDistanceSets ) {
chars = fixedDistanceSets [i ].Chars
}
invalidChars := len (chars ) != 1 || fixedDistanceSets [i ].Negated
if invalidChars || (i > 0 && fixedDistanceSets [i ].Distance != fixedDistanceSets [i -1 ].Distance +1 ) {
bestLen := 2
if best != nil {
bestLen = len (best .S )
}
if start != -1 && i -start >= bestLen {
best = &FixedDistanceLiteral {
S : vsb .String (),
Distance : fixedDistanceSets [start ].Distance ,
}
}
vsb .Reset ()
start = -1
if invalidChars {
continue
}
}
if start == -1 {
start = i
}
vsb .WriteRune (chars [0 ])
}
}
return best
}
The pages are generated with Golds v0.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 .