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+ s . CopyTo ( _chars . Slice ( index ) ) ;
160+ _pos += count ;
161+ }
162+
163+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
164+ public void Append ( char c )
165+ {
166+ int pos = _pos ;
167+ Span < char > chars = _chars ;
168+ if ( ( uint ) pos < ( uint ) chars . Length ) {
169+ chars [ pos ] = c ;
170+ _pos = pos + 1 ;
171+ }
172+ else {
173+ GrowAndAppend ( c ) ;
174+ }
175+ }
176+
177+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
178+ public void Append ( string ? s )
179+ {
180+ if ( s == null ) {
181+ return ;
182+ }
183+
184+ int pos = _pos ;
185+ if ( s . Length == 1 &&
186+ ( uint ) pos < ( uint ) _chars
187+ . Length ) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc.
188+ {
189+ _chars [ pos ] = s [ 0 ] ;
190+ _pos = pos + 1 ;
191+ }
192+ else {
193+ AppendSlow ( s ) ;
194+ }
195+ }
196+
197+ private void AppendSlow ( string s )
198+ {
199+ int pos = _pos ;
200+ if ( pos > _chars . Length - s . Length ) {
201+ Grow ( s . Length ) ;
202+ }
203+
204+ s . CopyTo ( _chars . Slice ( pos ) ) ;
205+ _pos += s . Length ;
206+ }
207+
208+ public void Append ( char c , int count )
209+ {
210+ if ( _pos > _chars . Length - count ) {
211+ Grow ( count ) ;
212+ }
213+
214+ Span < char > dst = _chars . Slice ( _pos , count ) ;
215+ for ( int i = 0 ; i < dst . Length ; i ++ ) {
216+ dst [ i ] = c ;
217+ }
218+
219+ _pos += count ;
220+ }
221+
222+ public void Append ( ReadOnlySpan < char > value )
223+ {
224+ int pos = _pos ;
225+ if ( pos > _chars . Length - value . Length ) {
226+ Grow ( value . Length ) ;
227+ }
228+
229+ value . CopyTo ( _chars . Slice ( _pos ) ) ;
230+ _pos += value . Length ;
231+ }
232+
233+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
234+ public Span < char > AppendSpan ( int length )
235+ {
236+ int origPos = _pos ;
237+ if ( origPos > _chars . Length - length ) {
238+ Grow ( length ) ;
239+ }
240+
241+ _pos = origPos + length ;
242+ return _chars . Slice ( origPos , length ) ;
243+ }
244+
245+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
246+ private void GrowAndAppend ( char c )
247+ {
248+ Grow ( 1 ) ;
249+ Append ( c ) ;
250+ }
251+
252+ /// <summary>
253+ /// Resize the internal buffer either by doubling current buffer size or
254+ /// by adding <paramref name="additionalCapacityBeyondPos"/> to
255+ /// <see cref="_pos"/> whichever is greater.
256+ /// </summary>
257+ /// <param name="additionalCapacityBeyondPos">
258+ /// Number of chars requested beyond current position.
259+ /// </param>
260+ [ MethodImpl ( MethodImplOptions . NoInlining ) ]
261+ private void Grow ( int additionalCapacityBeyondPos )
262+ {
263+ Debug . Assert ( additionalCapacityBeyondPos > 0 ) ;
264+ Debug . Assert ( _pos > _chars . Length - additionalCapacityBeyondPos ,
265+ "Grow called incorrectly, no resize is needed." ) ;
266+
267+ const uint ArrayMaxLength = 0x7FFFFFC7 ; // same as Array.MaxLength
268+
269+ // Increase to at least the required size (_pos + additionalCapacityBeyondPos), but try
270+ // to double the size if possible, bounding the doubling to not go beyond the max array length.
271+ int newCapacity = ( int ) Math . Max (
272+ ( uint ) ( _pos + additionalCapacityBeyondPos ) ,
273+ Math . Min ( ( uint ) _chars . Length * 2 , ArrayMaxLength ) ) ;
274+
275+ // Make sure to let Rent throw an exception if the caller has a bug and the desired capacity is negative.
276+ // This could also go negative if the actual required length wraps around.
277+ char [ ] poolArray = ArrayPool < char > . Shared . Rent ( newCapacity ) ;
278+
279+ _chars . Slice ( 0 , _pos ) . CopyTo ( poolArray ) ;
280+
281+ char [ ] ? toReturn = _arrayToReturnToPool ;
282+ _chars = _arrayToReturnToPool = poolArray ;
283+ if ( toReturn != null ) {
284+ ArrayPool < char > . Shared . Return ( toReturn ) ;
285+ }
286+ }
287+
288+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
289+ public void Dispose ( )
290+ {
291+ char [ ] ? toReturn = _arrayToReturnToPool ;
292+ this = default ; // for safety, to avoid using pooled array if this instance is erroneously appended to again
293+ if ( toReturn != null ) {
294+ ArrayPool < char > . Shared . Return ( toReturn ) ;
295+ }
296+ }
297+ }
298+ }
0 commit comments