chroma-markdown/vendor/github.com/dlclark/regexp2/replace.go

178 lines
4.0 KiB
Go
Raw Normal View History

2017-10-22 05:37:38 +02:00
package regexp2
import (
"bytes"
"errors"
"github.com/dlclark/regexp2/syntax"
)
const (
replaceSpecials = 4
replaceLeftPortion = -1
replaceRightPortion = -2
replaceLastGroup = -3
replaceWholeString = -4
)
// MatchEvaluator is a function that takes a match and returns a replacement string to be used
type MatchEvaluator func(Match) string
// Three very similar algorithms appear below: replace (pattern),
// replace (evaluator), and split.
// Replace Replaces all occurrences of the regex in the string with the
// replacement pattern.
//
// Note that the special case of no matches is handled on its own:
// with no matches, the input string is returned unchanged.
// The right-to-left case is split out because StringBuilder
// doesn't handle right-to-left string building directly very well.
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
}
m, err := regex.FindStringMatchStartingAt(input, startAt)
if err != nil {
return "", err
}
if m == nil {
return input, nil
}
buf := &bytes.Buffer{}
text := m.text
if !regex.RightToLeft() {
prevat := 0
for m != nil {
if m.Index != prevat {
buf.WriteString(string(text[prevat:m.Index]))
}
prevat = m.Index + m.Length
if evaluator == nil {
replacementImpl(data, buf, m)
} else {
buf.WriteString(evaluator(*m))
}
count--
if count == 0 {
break
}
m, err = regex.FindNextMatch(m)
if err != nil {
return "", nil
}
}
if prevat < len(text) {
buf.WriteString(string(text[prevat:]))
}
} else {
prevat := len(text)
var al []string
for m != nil {
if m.Index+m.Length != prevat {
al = append(al, string(text[m.Index+m.Length:prevat]))
}
prevat = m.Index
if evaluator == nil {
replacementImplRTL(data, &al, m)
} else {
al = append(al, evaluator(*m))
}
count--
if count == 0 {
break
}
m, err = regex.FindNextMatch(m)
if err != nil {
return "", nil
}
}
if prevat > 0 {
buf.WriteString(string(text[:prevat]))
}
for i := len(al) - 1; i >= 0; i-- {
buf.WriteString(al[i])
}
}
return buf.String(), nil
}
// Given a Match, emits into the StringBuilder the evaluated
// substitution pattern.
func replacementImpl(data *syntax.ReplacerData, buf *bytes.Buffer, m *Match) {
for _, r := range data.Rules {
if r >= 0 { // string lookup
buf.WriteString(data.Strings[r])
} else if r < -replaceSpecials { // group lookup
m.groupValueAppendToBuf(-replaceSpecials-1-r, buf)
} else {
switch -replaceSpecials - 1 - r { // special insertion patterns
case replaceLeftPortion:
for i := 0; i < m.Index; i++ {
buf.WriteRune(m.text[i])
}
case replaceRightPortion:
for i := m.Index + m.Length; i < len(m.text); i++ {
buf.WriteRune(m.text[i])
}
case replaceLastGroup:
m.groupValueAppendToBuf(m.GroupCount()-1, buf)
case replaceWholeString:
for i := 0; i < len(m.text); i++ {
buf.WriteRune(m.text[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 { // string lookup
l = append(l, data.Strings[r])
} else if r < -replaceSpecials { // group lookup
m.groupValueAppendToBuf(-replaceSpecials-1-r, buf)
l = append(l, buf.String())
} else {
switch -replaceSpecials - 1 - r { // special insertion patterns
case replaceLeftPortion:
for i := 0; i < m.Index; i++ {
buf.WriteRune(m.text[i])
}
case replaceRightPortion:
for i := m.Index + m.Length; i < len(m.text); i++ {
buf.WriteRune(m.text[i])
}
case replaceLastGroup:
m.groupValueAppendToBuf(m.GroupCount()-1, buf)
case replaceWholeString:
for i := 0; i < len(m.text); i++ {
buf.WriteRune(m.text[i])
}
}
l = append(l, buf.String())
}
}
*al = l
}