1package regexp2
2
3import (
4	"bytes"
5	"errors"
6
7	"github.com/dlclark/regexp2/syntax"
8)
9
10const (
11	replaceSpecials     = 4
12	replaceLeftPortion  = -1
13	replaceRightPortion = -2
14	replaceLastGroup    = -3
15	replaceWholeString  = -4
16)
17
18// MatchEvaluator is a function that takes a match and returns a replacement string to be used
19type MatchEvaluator func(Match) string
20
21// Three very similar algorithms appear below: replace (pattern),
22// replace (evaluator), and split.
23
24// Replace Replaces all occurrences of the regex in the string with the
25// replacement pattern.
26//
27// Note that the special case of no matches is handled on its own:
28// with no matches, the input string is returned unchanged.
29// The right-to-left case is split out because StringBuilder
30// doesn't handle right-to-left string building directly very well.
31func replace(regex *Regexp, data *syntax.ReplacerData, evaluator MatchEvaluator, input string, startAt, count int) (string, error) {
32	if count < -1 {
33		return "", errors.New("Count too small")
34	}
35	if count == 0 {
36		return "", nil
37	}
38
39	m, err := regex.FindStringMatchStartingAt(input, startAt)
40
41	if err != nil {
42		return "", err
43	}
44	if m == nil {
45		return input, nil
46	}
47
48	buf := &bytes.Buffer{}
49	text := m.text
50
51	if !regex.RightToLeft() {
52		prevat := 0
53		for m != nil {
54			if m.Index != prevat {
55				buf.WriteString(string(text[prevat:m.Index]))
56			}
57			prevat = m.Index + m.Length
58			if evaluator == nil {
59				replacementImpl(data, buf, m)
60			} else {
61				buf.WriteString(evaluator(*m))
62			}
63
64			count--
65			if count == 0 {
66				break
67			}
68			m, err = regex.FindNextMatch(m)
69			if err != nil {
70				return "", nil
71			}
72		}
73
74		if prevat < len(text) {
75			buf.WriteString(string(text[prevat:]))
76		}
77	} else {
78		prevat := len(text)
79		var al []string
80
81		for m != nil {
82			if m.Index+m.Length != prevat {
83				al = append(al, string(text[m.Index+m.Length:prevat]))
84			}
85			prevat = m.Index
86			if evaluator == nil {
87				replacementImplRTL(data, &al, m)
88			} else {
89				al = append(al, evaluator(*m))
90			}
91
92			count--
93			if count == 0 {
94				break
95			}
96			m, err = regex.FindNextMatch(m)
97			if err != nil {
98				return "", nil
99			}
100		}
101
102		if prevat > 0 {
103			buf.WriteString(string(text[:prevat]))
104		}
105
106		for i := len(al) - 1; i >= 0; i-- {
107			buf.WriteString(al[i])
108		}
109	}
110
111	return buf.String(), nil
112}
113
114// Given a Match, emits into the StringBuilder the evaluated
115// substitution pattern.
116func replacementImpl(data *syntax.ReplacerData, buf *bytes.Buffer, m *Match) {
117	for _, r := range data.Rules {
118
119		if r >= 0 { // string lookup
120			buf.WriteString(data.Strings[r])
121		} else if r < -replaceSpecials { // group lookup
122			m.groupValueAppendToBuf(-replaceSpecials-1-r, buf)
123		} else {
124			switch -replaceSpecials - 1 - r { // special insertion patterns
125			case replaceLeftPortion:
126				for i := 0; i < m.Index; i++ {
127					buf.WriteRune(m.text[i])
128				}
129			case replaceRightPortion:
130				for i := m.Index + m.Length; i < len(m.text); i++ {
131					buf.WriteRune(m.text[i])
132				}
133			case replaceLastGroup:
134				m.groupValueAppendToBuf(m.GroupCount()-1, buf)
135			case replaceWholeString:
136				for i := 0; i < len(m.text); i++ {
137					buf.WriteRune(m.text[i])
138				}
139			}
140		}
141	}
142}
143
144func replacementImplRTL(data *syntax.ReplacerData, al *[]string, m *Match) {
145	l := *al
146	buf := &bytes.Buffer{}
147
148	for _, r := range data.Rules {
149		buf.Reset()
150		if r >= 0 { // string lookup
151			l = append(l, data.Strings[r])
152		} else if r < -replaceSpecials { // group lookup
153			m.groupValueAppendToBuf(-replaceSpecials-1-r, buf)
154			l = append(l, buf.String())
155		} else {
156			switch -replaceSpecials - 1 - r { // special insertion patterns
157			case replaceLeftPortion:
158				for i := 0; i < m.Index; i++ {
159					buf.WriteRune(m.text[i])
160				}
161			case replaceRightPortion:
162				for i := m.Index + m.Length; i < len(m.text); i++ {
163					buf.WriteRune(m.text[i])
164				}
165			case replaceLastGroup:
166				m.groupValueAppendToBuf(m.GroupCount()-1, buf)
167			case replaceWholeString:
168				for i := 0; i < len(m.text); i++ {
169					buf.WriteRune(m.text[i])
170				}
171			}
172			l = append(l, buf.String())
173		}
174	}
175
176	*al = l
177}
178