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