package regexp2
import (
"bytes"
"errors"
"github.com/dlclark/regexp2/v2/syntax"
)
const (
replaceSpecials = 4
replaceLeftPortion = -1
replaceRightPortion = -2
replaceLastGroup = -3
replaceWholeString = -4
)
type MatchEvaluator func (Match ) string
func writeRunes(buf *bytes .Buffer , text []rune , start , end int ) {
for i := start ; i < end ; i ++ {
buf .WriteRune (text [i ])
}
}
func compactBalancedMatches(m *Match ) {
for cap := 0 ; cap < len (m .matchcount ); cap ++ {
limit := m .matchcount [cap ] * 2
matcharray := m .matches [cap ]
var i , j int
for i = 0 ; i < limit ; i ++ {
if matcharray [i ] < 0 {
break
}
}
for j = i ; i < limit ; i ++ {
if matcharray [i ] < 0 {
j --
} else {
if i != j {
matcharray [j ] = matcharray [i ]
}
j ++
}
}
m .matchcount [cap ] = j / 2
}
m .balancing = false
}
func replace(regex *Regexp , data *syntax .ReplacerData , evaluator MatchEvaluator , input string , startAt , count int ) (string , error ) {
if count < -1 {
return "" , errors .New ("count too small" )
}
if count == 0 {
return "" , nil
}
if evaluator == nil {
if !regex .RightToLeft () {
return replaceRunnerLTR (regex , data , input , startAt , count )
}
return replaceRunnerRTL (regex , data , input , startAt , count )
}
m , err := regex .FindStringMatchStartingAt (input , startAt )
if err != nil {
return "" , err
}
if m == nil {
return input , nil
}
buf := &bytes .Buffer {}
text := m .text .runes
if !regex .RightToLeft () {
prevat := 0
for m != nil {
if m .RuneIndex != prevat {
buf .WriteString (string (text [prevat :m .RuneIndex ]))
}
prevat = m .RuneIndex + m .RuneLength
buf .WriteString (evaluator (*m ))
count --
if count == 0 {
break
}
m , err = regex .FindNextMatch (m )
if err != nil {
return "" , err
}
}
if prevat < len (text ) {
buf .WriteString (string (text [prevat :]))
}
} else {
prevat := len (text )
var al []string
for m != nil {
if m .RuneIndex +m .RuneLength != prevat {
al = append (al , string (text [m .RuneIndex +m .RuneLength :prevat ]))
}
prevat = m .RuneIndex
al = append (al , evaluator (*m ))
count --
if count == 0 {
break
}
m , err = regex .FindNextMatch (m )
if err != nil {
return "" , err
}
}
if prevat > 0 {
buf .WriteString (string (text [:prevat ]))
}
for i := len (al ) - 1 ; i >= 0 ; i -- {
buf .WriteString (al [i ])
}
}
return buf .String (), nil
}
func replaceRunnerLTR(regex *Regexp , data *syntax .ReplacerData , input string , startAt , count int ) (string , error ) {
if startAt > len (input ) {
return "" , errors .New ("startAt must be less than the length of the input string" )
}
runner := regex .getRunner ()
text , runeStart , pooledText := runner .decodeStringWithStart (input , startAt )
textInfo := newStringMatchText (input , text )
defer func () {
regex .putRunner (runner )
if pooledText != nil {
pooledRuneBuffers .put (pooledText )
}
}()
if startAt >= 0 && runeStart < 0 {
return "" , errors .New ("startAt must align to the start of a valid rune in the input string" )
}
if runeStart < 0 {
runeStart = 0
}
m , err := runner .scan (text , textInfo , runeStart , -1 , true , regex .MatchTimeout )
if err != nil {
return "" , err
}
if m == nil {
return input , nil
}
buf , pooledBuf := getPooledReplaceBuffer (len (input ), regex .optimizations .MaxCachedReplaceBufferLength )
if pooledBuf != nil {
defer putPooledReplaceBuffer (buf , pooledBuf )
}
prevat := 0
for m != nil {
if m .balancing {
compactBalancedMatches (m )
}
if m .RuneIndex != prevat {
writeRunes (buf , text , prevat , m .RuneIndex )
}
prevat = m .RuneIndex + m .RuneLength
replacementImpl (data , buf , m )
count --
if count == 0 {
break
}
m , err = runner .scan (text , textInfo , m .textpos , m .RuneLength , true , regex .MatchTimeout )
if err != nil {
return "" , err
}
}
if prevat < len (text ) {
writeRunes (buf , text , prevat , len (text ))
}
return buf .String (), nil
}
func replaceRunnerRTL(regex *Regexp , data *syntax .ReplacerData , input string , startAt , count int ) (string , error ) {
if startAt > len (input ) {
return "" , errors .New ("startAt must be less than the length of the input string" )
}
runner := regex .getRunner ()
text , runeStart , pooledText := runner .decodeStringWithStart (input , startAt )
textInfo := newStringMatchText (input , text )
defer func () {
regex .putRunner (runner )
if pooledText != nil {
pooledRuneBuffers .put (pooledText )
}
}()
if startAt >= 0 && runeStart < 0 {
return "" , errors .New ("startAt must align to the start of a valid rune in the input string" )
}
if runeStart < 0 {
runeStart = len (text )
}
m , err := runner .scan (text , textInfo , runeStart , -1 , true , regex .MatchTimeout )
if err != nil {
return "" , err
}
if m == nil {
return input , nil
}
buf , pooledBuf := getPooledReplaceBuffer (len (input ), regex .optimizations .MaxCachedReplaceBufferLength )
if pooledBuf != nil {
defer putPooledReplaceBuffer (buf , pooledBuf )
}
prevat := len (text )
var al []string
for m != nil {
if m .balancing {
compactBalancedMatches (m )
}
if m .RuneIndex +m .RuneLength != prevat {
al = append (al , string (text [m .RuneIndex +m .RuneLength :prevat ]))
}
prevat = m .RuneIndex
replacementImplRTL (data , &al , m )
count --
if count == 0 {
break
}
m , err = runner .scan (text , textInfo , m .textpos , m .RuneLength , true , regex .MatchTimeout )
if err != nil {
return "" , err
}
}
if prevat > 0 {
writeRunes (buf , text , 0 , prevat )
}
for i := len (al ) - 1 ; i >= 0 ; i -- {
buf .WriteString (al [i ])
}
return buf .String (), nil
}
func replacementImpl(data *syntax .ReplacerData , buf *bytes .Buffer , m *Match ) {
for _ , r := range data .Rules {
if r >= 0 {
buf .WriteString (data .Strings [r ])
} else if r < -replaceSpecials {
m .groupValueAppendToBuf (-replaceSpecials -1 -r , buf )
} else {
switch -replaceSpecials - 1 - r {
case replaceLeftPortion :
for i := 0 ; i < m .RuneIndex ; i ++ {
buf .WriteRune (m .text .runes [i ])
}
case replaceRightPortion :
for i := m .RuneIndex + m .RuneLength ; i < len (m .text .runes ); i ++ {
buf .WriteRune (m .text .runes [i ])
}
case replaceLastGroup :
m .groupValueAppendToBuf (m .GroupCount ()-1 , buf )
case replaceWholeString :
for i := 0 ; i < len (m .text .runes ); i ++ {
buf .WriteRune (m .text .runes [i ])
}
}
}
}
}
func replacementImplRTL(data *syntax .ReplacerData , al *[]string , m *Match ) {
l := *al
buf := &bytes .Buffer {}
for _ , r := range data .Rules {
buf .Reset ()
if r >= 0 {
l = append (l , data .Strings [r ])
} else if r < -replaceSpecials {
m .groupValueAppendToBuf (-replaceSpecials -1 -r , buf )
l = append (l , buf .String ())
} else {
switch -replaceSpecials - 1 - r {
case replaceLeftPortion :
for i := 0 ; i < m .RuneIndex ; i ++ {
buf .WriteRune (m .text .runes [i ])
}
case replaceRightPortion :
for i := m .RuneIndex + m .RuneLength ; i < len (m .text .runes ); i ++ {
buf .WriteRune (m .text .runes [i ])
}
case replaceLastGroup :
m .groupValueAppendToBuf (m .GroupCount ()-1 , buf )
case replaceWholeString :
for i := 0 ; i < len (m .text .runes ); i ++ {
buf .WriteRune (m .text .runes [i ])
}
}
l = append (l , buf .String ())
}
}
*al = l
}
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 .