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