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