| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System.Buffers; |
| | | 5 | | using System.Diagnostics; |
| | | 6 | | using System.Runtime.CompilerServices; |
| | | 7 | | using System.Runtime.InteropServices; |
| | | 8 | | |
| | | 9 | | #nullable enable |
| | | 10 | | |
| | | 11 | | namespace System.Text |
| | | 12 | | { |
| | | 13 | | internal ref partial struct ValueStringBuilder |
| | | 14 | | { |
| | | 15 | | private char[]? _arrayToReturnToPool; |
| | | 16 | | private Span<char> _chars; |
| | | 17 | | private int _pos; |
| | | 18 | | |
| | | 19 | | public ValueStringBuilder(Span<char> initialBuffer) |
| | 83980 | 20 | | { |
| | 83980 | 21 | | _arrayToReturnToPool = null; |
| | 83980 | 22 | | _chars = initialBuffer; |
| | 83980 | 23 | | _pos = 0; |
| | 83980 | 24 | | } |
| | | 25 | | |
| | | 26 | | public ValueStringBuilder(int initialCapacity) |
| | 18 | 27 | | { |
| | 18 | 28 | | _arrayToReturnToPool = ArrayPool<char>.Shared.Rent(initialCapacity); |
| | 18 | 29 | | _chars = _arrayToReturnToPool; |
| | 18 | 30 | | _pos = 0; |
| | 18 | 31 | | } |
| | | 32 | | |
| | | 33 | | public int Length |
| | | 34 | | { |
| | 0 | 35 | | get => _pos; |
| | | 36 | | set |
| | 0 | 37 | | { |
| | 0 | 38 | | Debug.Assert(value >= 0); |
| | 0 | 39 | | Debug.Assert(value <= _chars.Length); |
| | 0 | 40 | | _pos = value; |
| | 0 | 41 | | } |
| | | 42 | | } |
| | | 43 | | |
| | 0 | 44 | | public int Capacity => _chars.Length; |
| | | 45 | | |
| | | 46 | | public void EnsureCapacity(int capacity) |
| | 0 | 47 | | { |
| | | 48 | | // This is not expected to be called this with negative capacity |
| | 0 | 49 | | Debug.Assert(capacity >= 0); |
| | | 50 | | |
| | | 51 | | // If the caller has a bug and calls this with negative capacity, make sure to call Grow to throw an excepti |
| | 0 | 52 | | if ((uint)capacity > (uint)_chars.Length) |
| | 0 | 53 | | Grow(capacity - _pos); |
| | 0 | 54 | | } |
| | | 55 | | |
| | | 56 | | /// <summary> |
| | | 57 | | /// Ensures that the builder is terminated with a NUL character. |
| | | 58 | | /// </summary> |
| | | 59 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 60 | | public void NullTerminate() |
| | | 61 | | { |
| | | 62 | | EnsureCapacity(_pos + 1); |
| | | 63 | | _chars[_pos] = '\0'; |
| | | 64 | | } |
| | | 65 | | |
| | | 66 | | /// <summary> |
| | | 67 | | /// Get a pinnable reference to the builder. |
| | | 68 | | /// Does not ensure there is a null char after <see cref="Length"/> |
| | | 69 | | /// This overload is pattern matched in the C# 7.3+ compiler so you can omit |
| | | 70 | | /// the explicit method call, and write eg "fixed (char* c = builder)" |
| | | 71 | | /// </summary> |
| | | 72 | | public ref char GetPinnableReference() |
| | | 73 | | { |
| | | 74 | | return ref MemoryMarshal.GetReference(_chars); |
| | | 75 | | } |
| | | 76 | | |
| | | 77 | | public ref char this[int index] |
| | | 78 | | { |
| | | 79 | | get |
| | | 80 | | { |
| | | 81 | | Debug.Assert(index < _pos); |
| | | 82 | | return ref _chars[index]; |
| | | 83 | | } |
| | | 84 | | } |
| | | 85 | | |
| | | 86 | | public override string ToString() |
| | 83668 | 87 | | { |
| | 83668 | 88 | | string s = _chars.Slice(0, _pos).ToString(); |
| | 83668 | 89 | | Dispose(); |
| | 83668 | 90 | | return s; |
| | 83668 | 91 | | } |
| | | 92 | | |
| | | 93 | | /// <summary>Returns the underlying storage of the builder.</summary> |
| | 0 | 94 | | public Span<char> RawChars => _chars; |
| | | 95 | | |
| | 0 | 96 | | public ReadOnlySpan<char> AsSpan() => _chars.Slice(0, _pos); |
| | | 97 | | public ReadOnlySpan<char> AsSpan(int start) => _chars.Slice(start, _pos - start); |
| | | 98 | | public ReadOnlySpan<char> AsSpan(int start, int length) => _chars.Slice(start, length); |
| | | 99 | | |
| | | 100 | | public void Insert(int index, char value, int count) |
| | | 101 | | { |
| | | 102 | | if (_pos > _chars.Length - count) |
| | | 103 | | { |
| | | 104 | | Grow(count); |
| | | 105 | | } |
| | | 106 | | |
| | | 107 | | int remaining = _pos - index; |
| | | 108 | | _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); |
| | | 109 | | _chars.Slice(index, count).Fill(value); |
| | | 110 | | _pos += count; |
| | | 111 | | } |
| | | 112 | | |
| | | 113 | | public void Insert(int index, string? s) |
| | | 114 | | { |
| | | 115 | | if (s == null) |
| | | 116 | | { |
| | | 117 | | return; |
| | | 118 | | } |
| | | 119 | | |
| | | 120 | | int count = s.Length; |
| | | 121 | | |
| | | 122 | | if (_pos > (_chars.Length - count)) |
| | | 123 | | { |
| | | 124 | | Grow(count); |
| | | 125 | | } |
| | | 126 | | |
| | | 127 | | int remaining = _pos - index; |
| | | 128 | | _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count)); |
| | | 129 | | s |
| | | 130 | | #if !NET |
| | | 131 | | .AsSpan() |
| | | 132 | | #endif |
| | | 133 | | .CopyTo(_chars.Slice(index)); |
| | | 134 | | _pos += count; |
| | | 135 | | } |
| | | 136 | | |
| | | 137 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 138 | | public void Append(char c) |
| | 178254 | 139 | | { |
| | 178254 | 140 | | int pos = _pos; |
| | 178254 | 141 | | Span<char> chars = _chars; |
| | 178254 | 142 | | if ((uint)pos < (uint)chars.Length) |
| | 178038 | 143 | | { |
| | 178038 | 144 | | chars[pos] = c; |
| | 178038 | 145 | | _pos = pos + 1; |
| | 178038 | 146 | | } |
| | | 147 | | else |
| | 216 | 148 | | { |
| | 216 | 149 | | GrowAndAppend(c); |
| | 216 | 150 | | } |
| | 178254 | 151 | | } |
| | | 152 | | |
| | | 153 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 154 | | public void Append(string? s) |
| | 219906 | 155 | | { |
| | 219906 | 156 | | if (s == null) |
| | 0 | 157 | | { |
| | 0 | 158 | | return; |
| | | 159 | | } |
| | | 160 | | |
| | 219906 | 161 | | int pos = _pos; |
| | 219906 | 162 | | if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from Numbe |
| | 20638 | 163 | | { |
| | 20638 | 164 | | _chars[pos] = s[0]; |
| | 20638 | 165 | | _pos = pos + 1; |
| | 20638 | 166 | | } |
| | | 167 | | else |
| | 199268 | 168 | | { |
| | 199268 | 169 | | AppendSlow(s); |
| | 199268 | 170 | | } |
| | 219906 | 171 | | } |
| | | 172 | | |
| | | 173 | | private void AppendSlow(string s) |
| | 199268 | 174 | | { |
| | 199268 | 175 | | int pos = _pos; |
| | 199268 | 176 | | if (pos > _chars.Length - s.Length) |
| | 2138 | 177 | | { |
| | 2138 | 178 | | Grow(s.Length); |
| | 2138 | 179 | | } |
| | | 180 | | |
| | 199268 | 181 | | s |
| | 199268 | 182 | | #if !NET |
| | 199268 | 183 | | .AsSpan() |
| | 199268 | 184 | | #endif |
| | 199268 | 185 | | .CopyTo(_chars.Slice(pos)); |
| | 199268 | 186 | | _pos += s.Length; |
| | 199268 | 187 | | } |
| | | 188 | | |
| | | 189 | | public void Append(char c, int count) |
| | | 190 | | { |
| | | 191 | | if (_pos > _chars.Length - count) |
| | | 192 | | { |
| | | 193 | | Grow(count); |
| | | 194 | | } |
| | | 195 | | |
| | | 196 | | Span<char> dst = _chars.Slice(_pos, count); |
| | | 197 | | for (int i = 0; i < dst.Length; i++) |
| | | 198 | | { |
| | | 199 | | dst[i] = c; |
| | | 200 | | } |
| | | 201 | | _pos += count; |
| | | 202 | | } |
| | | 203 | | |
| | | 204 | | public void Append(scoped ReadOnlySpan<char> value) |
| | 1142 | 205 | | { |
| | 1142 | 206 | | int pos = _pos; |
| | 1142 | 207 | | if (pos > _chars.Length - value.Length) |
| | 0 | 208 | | { |
| | 0 | 209 | | Grow(value.Length); |
| | 0 | 210 | | } |
| | | 211 | | |
| | 1142 | 212 | | value.CopyTo(_chars.Slice(_pos)); |
| | 1142 | 213 | | _pos += value.Length; |
| | 1142 | 214 | | } |
| | | 215 | | |
| | | 216 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 217 | | public Span<char> AppendSpan(int length) |
| | 0 | 218 | | { |
| | 0 | 219 | | int origPos = _pos; |
| | 0 | 220 | | if (origPos > _chars.Length - length) |
| | 0 | 221 | | { |
| | 0 | 222 | | Grow(length); |
| | 0 | 223 | | } |
| | | 224 | | |
| | 0 | 225 | | _pos = origPos + length; |
| | 0 | 226 | | return _chars.Slice(origPos, length); |
| | 0 | 227 | | } |
| | | 228 | | |
| | | 229 | | [MethodImpl(MethodImplOptions.NoInlining)] |
| | | 230 | | private void GrowAndAppend(char c) |
| | 216 | 231 | | { |
| | 216 | 232 | | Grow(1); |
| | 216 | 233 | | Append(c); |
| | 216 | 234 | | } |
| | | 235 | | |
| | | 236 | | /// <summary> |
| | | 237 | | /// Resize the internal buffer either by doubling current buffer size or |
| | | 238 | | /// by adding <paramref name="additionalCapacityBeyondPos"/> to |
| | | 239 | | /// <see cref="_pos"/> whichever is greater. |
| | | 240 | | /// </summary> |
| | | 241 | | /// <param name="additionalCapacityBeyondPos"> |
| | | 242 | | /// Number of chars requested beyond current position. |
| | | 243 | | /// </param> |
| | | 244 | | [MethodImpl(MethodImplOptions.NoInlining)] |
| | | 245 | | private void Grow(int additionalCapacityBeyondPos) |
| | 2354 | 246 | | { |
| | 2354 | 247 | | Debug.Assert(additionalCapacityBeyondPos > 0); |
| | 2354 | 248 | | Debug.Assert(_pos > _chars.Length - additionalCapacityBeyondPos, "Grow called incorrectly, no resize is need |
| | | 249 | | |
| | | 250 | | const uint ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength |
| | | 251 | | |
| | | 252 | | // Increase to at least the required size (_pos + additionalCapacityBeyondPos), but try |
| | | 253 | | // to double the size if possible, bounding the doubling to not go beyond the max array length. |
| | 2354 | 254 | | int newCapacity = (int)Math.Max( |
| | 2354 | 255 | | (uint)(_pos + additionalCapacityBeyondPos), |
| | 2354 | 256 | | Math.Min((uint)_chars.Length * 2, ArrayMaxLength)); |
| | | 257 | | |
| | | 258 | | // Make sure to let Rent throw an exception if the caller has a bug and the desired capacity is negative. |
| | | 259 | | // This could also go negative if the actual required length wraps around. |
| | 2354 | 260 | | char[] poolArray = ArrayPool<char>.Shared.Rent(newCapacity); |
| | | 261 | | |
| | 2354 | 262 | | _chars.Slice(0, _pos).CopyTo(poolArray); |
| | | 263 | | |
| | 2354 | 264 | | char[]? toReturn = _arrayToReturnToPool; |
| | 2354 | 265 | | _chars = _arrayToReturnToPool = poolArray; |
| | 2354 | 266 | | if (toReturn != null) |
| | 282 | 267 | | { |
| | 282 | 268 | | ArrayPool<char>.Shared.Return(toReturn); |
| | 282 | 269 | | } |
| | 2354 | 270 | | } |
| | | 271 | | |
| | | 272 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 273 | | public void Dispose() |
| | 83998 | 274 | | { |
| | 83998 | 275 | | char[]? toReturn = _arrayToReturnToPool; |
| | 83998 | 276 | | this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again |
| | 83998 | 277 | | if (toReturn != null) |
| | 2090 | 278 | | { |
| | 2090 | 279 | | ArrayPool<char>.Shared.Return(toReturn); |
| | 2090 | 280 | | } |
| | 83998 | 281 | | } |
| | | 282 | | } |
| | | 283 | | } |