-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathValueStringBuilder.Replace.cs
178 lines (158 loc) · 7.76 KB
/
ValueStringBuilder.Replace.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
using System.Runtime.CompilerServices;
using System.Text;
namespace LinkDotNet.StringBuilder;
public ref partial struct ValueStringBuilder
{
/// <summary>
/// Replaces all instances of one character with another in this builder.
/// </summary>
/// <param name="oldValue">The character to replace.</param>
/// <param name="newValue">The character to replace <paramref name="oldValue"/> with.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void Replace(char oldValue, char newValue) => Replace(oldValue, newValue, 0, Length);
/// <summary>
/// Replaces all instances of one character with another in this builder.
/// </summary>
/// <param name="oldValue">The character to replace.</param>
/// <param name="newValue">The character to replace <paramref name="oldValue"/> with.</param>
/// <param name="startIndex">The index to start in this builder.</param>
/// <param name="count">The number of characters to read in this builder.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void Replace(char oldValue, char newValue, int startIndex, int count)
{
ArgumentOutOfRangeException.ThrowIfLessThan(startIndex, 0);
ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex + count, Length);
buffer.Slice(startIndex, count).Replace(oldValue, newValue);
}
/// <summary>
/// Replaces all instances of one rune with another in this builder.
/// </summary>
/// <param name="oldValue">The rune to replace.</param>
/// <param name="newValue">The rune to replace <paramref name="oldValue"/> with.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Replace(Rune oldValue, Rune newValue) => Replace(oldValue, newValue, 0, Length);
/// <summary>
/// Replaces all instances of one rune with another in this builder.
/// </summary>
/// <param name="oldValue">The rune to replace.</param>
/// <param name="newValue">The rune to replace <paramref name="oldValue"/> with.</param>
/// <param name="startIndex">The index to start in this builder.</param>
/// <param name="count">The number of characters to read in this builder.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Replace(Rune oldValue, Rune newValue, int startIndex, int count)
{
Span<char> oldValueChars = stackalloc char[2];
var oldValueCharsWritten = oldValue.EncodeToUtf16(oldValueChars);
ReadOnlySpan<char> oldValueCharsSlice = oldValueChars[..oldValueCharsWritten];
Span<char> newValueChars = stackalloc char[2];
var newValueCharsWritten = newValue.EncodeToUtf16(newValueChars);
ReadOnlySpan<char> newValueCharsSlice = newValueChars[..newValueCharsWritten];
Replace(oldValueCharsSlice, newValueCharsSlice, startIndex, count);
}
/// <summary>
/// Replaces all instances of one string with another in this builder.
/// </summary>
/// <param name="oldValue">The string to replace.</param>
/// <param name="newValue">The string to replace <paramref name="oldValue"/> with.</param>
/// <remarks>
/// If <paramref name="newValue"/> is <c>empty</c>, instances of <paramref name="oldValue"/> are removed.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Replace(scoped ReadOnlySpan<char> oldValue, scoped ReadOnlySpan<char> newValue)
=> Replace(oldValue, newValue, 0, Length);
/// <summary>
/// Replaces all instances of one string with another in this builder.
/// </summary>
/// <param name="oldValue">The string to replace.</param>
/// <param name="newValue">The string to replace <paramref name="oldValue"/> with.</param>
/// <param name="startIndex">The index to start in this builder.</param>
/// <param name="count">The number of characters to read in this builder.</param>
/// <remarks>
/// If <paramref name="newValue"/> is <c>empty</c>, instances of <paramref name="oldValue"/> are removed.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Replace(scoped ReadOnlySpan<char> oldValue, scoped ReadOnlySpan<char> newValue, int startIndex, int count)
{
ArgumentOutOfRangeException.ThrowIfLessThan(startIndex, 0);
ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex + count, Length);
if (oldValue.IsEmpty || oldValue.Equals(newValue, StringComparison.Ordinal))
{
return;
}
if (oldValue.Length == 1 && newValue.Length == 1)
{
Replace(oldValue[0], newValue[0], startIndex, count);
return;
}
var index = startIndex;
var remainingChars = count;
while (remainingChars > 0)
{
var foundSubIndex = buffer.Slice(index, remainingChars).IndexOf(oldValue, StringComparison.Ordinal);
if (foundSubIndex < 0)
{
break;
}
index += foundSubIndex;
remainingChars -= foundSubIndex;
if (newValue.Length == oldValue.Length)
{
// Just replace the old slice
newValue.CopyTo(buffer[index..]);
}
else if (newValue.Length < oldValue.Length)
{
// Replace the old slice and trim the unused slice
newValue.CopyTo(buffer[index..]);
Remove(index + newValue.Length, oldValue.Length - newValue.Length);
}
else
{
// Replace the old slice and append the extra slice
newValue[..oldValue.Length].CopyTo(buffer[index..]);
Insert(index + oldValue.Length, newValue[oldValue.Length..]);
}
index += newValue.Length;
remainingChars -= oldValue.Length;
}
}
/// <summary>
/// Replaces all instances of one string with another in this builder.
/// </summary>
/// <param name="oldValue">The string to replace.</param>
/// <param name="newValue">Object to replace <paramref name="oldValue"/> with.</param>
/// <remarks>
/// If <paramref name="newValue"/> is from type <see cref="ISpanFormattable"/> an optimized version is taken.
/// Otherwise the ToString method is called.
/// </remarks>
/// /// <typeparam name="T">Any type.</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReplaceGeneric<T>(scoped ReadOnlySpan<char> oldValue, T newValue)
=> ReplaceGeneric(oldValue, newValue, 0, Length);
/// <summary>
/// Replaces all instances of one string with another in this builder.
/// </summary>
/// <param name="oldValue">The string to replace.</param>
/// <param name="newValue">Object to replace <paramref name="oldValue"/> with.</param>
/// <param name="startIndex">The index to start in this builder.</param>
/// <param name="count">The number of characters to read in this builder.</param>
/// <remarks>
/// If <paramref name="newValue"/> is <see cref="ISpanFormattable"/>, <c>TryFormat</c> is used.
/// Otherwise, <c>ToString</c> is used.
/// </remarks>
/// /// <typeparam name="T">Any type.</typeparam>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ReplaceGeneric<T>(scoped ReadOnlySpan<char> oldValue, T newValue, int startIndex, int count)
{
if (newValue is ISpanFormattable spanFormattable)
{
Span<char> tempBuffer = stackalloc char[128];
if (spanFormattable.TryFormat(tempBuffer, out var written, default, null))
{
Replace(oldValue, tempBuffer[..written], startIndex, count);
return;
}
}
Replace(oldValue, newValue?.ToString() ?? string.Empty, startIndex, count);
}
}