Skip to content

Commit ebc772c

Browse files
Update BigInteger and Complex to support UTF8 parsing and formatting (#117745)
1 parent 60a7500 commit ebc772c

File tree

11 files changed

+824
-198
lines changed

11 files changed

+824
-198
lines changed
Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
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<TChar>
14+
where TChar : unmanaged
15+
{
16+
private TChar[]? _arrayToReturnToPool;
17+
private Span<TChar> _chars;
18+
private int _pos;
19+
20+
public ValueStringBuilder(Span<TChar> initialBuffer)
21+
{
22+
Debug.Assert((typeof(TChar) == typeof(Utf8Char)) || (typeof(TChar) == typeof(Utf16Char)));
23+
24+
_arrayToReturnToPool = null;
25+
_chars = initialBuffer;
26+
_pos = 0;
27+
}
28+
29+
public ValueStringBuilder(int initialCapacity)
30+
{
31+
Debug.Assert((typeof(TChar) == typeof(Utf8Char)) || (typeof(TChar) == typeof(Utf16Char)));
32+
33+
_arrayToReturnToPool = ArrayPool<TChar>.Shared.Rent(initialCapacity);
34+
_chars = _arrayToReturnToPool;
35+
_pos = 0;
36+
}
37+
38+
public int Length
39+
{
40+
readonly get => _pos;
41+
set
42+
{
43+
Debug.Assert(value >= 0);
44+
Debug.Assert(value <= _chars.Length);
45+
_pos = value;
46+
}
47+
}
48+
49+
public readonly int Capacity => _chars.Length;
50+
51+
public void EnsureCapacity(int capacity)
52+
{
53+
// This is not expected to be called this with negative capacity
54+
Debug.Assert(capacity >= 0);
55+
56+
// If the caller has a bug and calls this with negative capacity, make sure to call Grow to throw an exception.
57+
if ((uint)capacity > (uint)_chars.Length)
58+
Grow(capacity - _pos);
59+
}
60+
61+
/// <summary>
62+
/// Get a pinnable reference to the builder.
63+
/// Does not ensure there is a null TChar after <see cref="Length"/>
64+
/// This overload is pattern matched in the C# 7.3+ compiler so you can omit
65+
/// the explicit method call, and write eg "fixed (TChar* c = builder)"
66+
/// </summary>
67+
public readonly ref TChar GetPinnableReference()
68+
{
69+
return ref MemoryMarshal.GetReference(_chars);
70+
}
71+
72+
/// <summary>
73+
/// Get a pinnable reference to the builder.
74+
/// </summary>
75+
/// <param name="terminate">Ensures that the builder has a null TChar after <see cref="Length"/></param>
76+
public ref TChar GetPinnableReference(bool terminate)
77+
{
78+
if (terminate)
79+
{
80+
EnsureCapacity(Length + 1);
81+
_chars[Length] = default;
82+
}
83+
return ref MemoryMarshal.GetReference(_chars);
84+
}
85+
86+
public readonly ref TChar this[int index]
87+
{
88+
get
89+
{
90+
Debug.Assert(index < _pos);
91+
return ref _chars[index];
92+
}
93+
}
94+
95+
public override string ToString()
96+
{
97+
string result;
98+
Span<TChar> slice = _chars.Slice(0, _pos);
99+
100+
if (typeof(TChar) == typeof(Utf8Char))
101+
{
102+
result = Encoding.UTF8.GetString(Unsafe.BitCast<ReadOnlySpan<TChar>, ReadOnlySpan<byte>>(slice));
103+
}
104+
else
105+
{
106+
Debug.Assert(typeof(TChar) == typeof(Utf16Char));
107+
result = Unsafe.BitCast<ReadOnlySpan<TChar>, ReadOnlySpan<char>>(slice).ToString();
108+
}
109+
110+
Dispose();
111+
return result;
112+
}
113+
114+
/// <summary>Returns the underlying storage of the builder.</summary>
115+
public readonly Span<TChar> RawChars => _chars;
116+
117+
/// <summary>
118+
/// Returns a span around the contents of the builder.
119+
/// </summary>
120+
/// <param name="terminate">Ensures that the builder has a null TChar after <see cref="Length"/></param>
121+
public ReadOnlySpan<TChar> AsSpan(bool terminate)
122+
{
123+
if (terminate)
124+
{
125+
EnsureCapacity(Length + 1);
126+
_chars[Length] = default;
127+
}
128+
return _chars.Slice(0, _pos);
129+
}
130+
131+
public readonly ReadOnlySpan<TChar> AsSpan() => _chars.Slice(0, _pos);
132+
public readonly ReadOnlySpan<TChar> AsSpan(int start) => _chars.Slice(start, _pos - start);
133+
public readonly ReadOnlySpan<TChar> AsSpan(int start, int length) => _chars.Slice(start, length);
134+
135+
public bool TryCopyTo(Span<TChar> destination, out int charsWritten)
136+
{
137+
if (_chars.Slice(0, _pos).TryCopyTo(destination))
138+
{
139+
charsWritten = _pos;
140+
Dispose();
141+
return true;
142+
}
143+
else
144+
{
145+
charsWritten = 0;
146+
Dispose();
147+
return false;
148+
}
149+
}
150+
151+
public void Insert(int index, TChar value, int count)
152+
{
153+
if (_pos > _chars.Length - count)
154+
{
155+
Grow(count);
156+
}
157+
158+
int remaining = _pos - index;
159+
_chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
160+
_chars.Slice(index, count).Fill(value);
161+
_pos += count;
162+
}
163+
164+
public void Insert(int index, ReadOnlySpan<TChar> text)
165+
{
166+
if (text.IsEmpty)
167+
{
168+
return;
169+
}
170+
171+
int count = text.Length;
172+
173+
if (_pos > (_chars.Length - count))
174+
{
175+
Grow(count);
176+
}
177+
178+
int remaining = _pos - index;
179+
_chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
180+
text.CopyTo(_chars.Slice(index));
181+
_pos += count;
182+
}
183+
184+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
185+
public void Append(TChar c)
186+
{
187+
int pos = _pos;
188+
Span<TChar> chars = _chars;
189+
if ((uint)pos < (uint)chars.Length)
190+
{
191+
chars[pos] = c;
192+
_pos = pos + 1;
193+
}
194+
else
195+
{
196+
GrowAndAppend(c);
197+
}
198+
}
199+
200+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
201+
public void Append(ReadOnlySpan<TChar> text)
202+
{
203+
if (text.IsEmpty)
204+
{
205+
return;
206+
}
207+
208+
int pos = _pos;
209+
if (text.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc.
210+
{
211+
_chars[pos] = text[0];
212+
_pos = pos + 1;
213+
}
214+
else
215+
{
216+
AppendSlow(text);
217+
}
218+
}
219+
220+
private void AppendSlow(ReadOnlySpan<TChar> text)
221+
{
222+
int pos = _pos;
223+
if (pos > _chars.Length - text.Length)
224+
{
225+
Grow(text.Length);
226+
}
227+
228+
text.CopyTo(_chars.Slice(pos));
229+
_pos += text.Length;
230+
}
231+
232+
public void Append(TChar c, int count)
233+
{
234+
if (_pos > _chars.Length - count)
235+
{
236+
Grow(count);
237+
}
238+
239+
Span<TChar> dst = _chars.Slice(_pos, count);
240+
for (int i = 0; i < dst.Length; i++)
241+
{
242+
dst[i] = c;
243+
}
244+
_pos += count;
245+
}
246+
247+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
248+
public Span<TChar> AppendSpan(int length)
249+
{
250+
int origPos = _pos;
251+
if (origPos > _chars.Length - length)
252+
{
253+
Grow(length);
254+
}
255+
256+
_pos = origPos + length;
257+
return _chars.Slice(origPos, length);
258+
}
259+
260+
[MethodImpl(MethodImplOptions.NoInlining)]
261+
private void GrowAndAppend(TChar c)
262+
{
263+
Grow(1);
264+
Append(c);
265+
}
266+
267+
/// <summary>
268+
/// Resize the internal buffer either by doubling current buffer size or
269+
/// by adding <paramref name="additionalCapacityBeyondPos"/> to
270+
/// <see cref="_pos"/> whichever is greater.
271+
/// </summary>
272+
/// <param name="additionalCapacityBeyondPos">
273+
/// Number of chars requested beyond current position.
274+
/// </param>
275+
[MethodImpl(MethodImplOptions.NoInlining)]
276+
private void Grow(int additionalCapacityBeyondPos)
277+
{
278+
Debug.Assert(additionalCapacityBeyondPos > 0);
279+
Debug.Assert(_pos > _chars.Length - additionalCapacityBeyondPos, "Grow called incorrectly, no resize is needed.");
280+
281+
const uint ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength
282+
283+
// Increase to at least the required size (_pos + additionalCapacityBeyondPos), but try
284+
// to double the size if possible, bounding the doubling to not go beyond the max array length.
285+
int newCapacity = (int)Math.Max(
286+
(uint)(_pos + additionalCapacityBeyondPos),
287+
Math.Min((uint)_chars.Length * 2, ArrayMaxLength));
288+
289+
// Make sure to let Rent throw an exception if the caller has a bug and the desired capacity is negative.
290+
// This could also go negative if the actual required length wraps around.
291+
TChar[] poolArray = ArrayPool<TChar>.Shared.Rent(newCapacity);
292+
293+
_chars.Slice(0, _pos).CopyTo(poolArray);
294+
295+
TChar[]? toReturn = _arrayToReturnToPool;
296+
_chars = _arrayToReturnToPool = poolArray;
297+
if (toReturn != null)
298+
{
299+
ArrayPool<TChar>.Shared.Return(toReturn);
300+
}
301+
}
302+
303+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
304+
public void Dispose()
305+
{
306+
TChar[]? toReturn = _arrayToReturnToPool;
307+
this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again
308+
if (toReturn != null)
309+
{
310+
ArrayPool<TChar>.Shared.Return(toReturn);
311+
}
312+
}
313+
}
314+
}

0 commit comments

Comments
 (0)