• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

neon-sunset / U8String / 5868414274

pending completion
5868414274

push

github

neon-sunset
feat: consolidate CopyTo/ToArray/List logic, naming and Rune methods

105 of 704 branches covered (14.91%)

Branch coverage included in aggregate %.

46 of 46 new or added lines in 6 files covered. (100.0%)

372 of 1547 relevant lines covered (24.05%)

460.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/U8String.Manipulation.cs
1
using System.Buffers;
2
using System.Text;
3
using U8Primitives.InteropServices;
4

5
namespace U8Primitives;
6

7
#pragma warning disable IDE0046, IDE0057 // Why: range slicing and ternary expressions do not produce desired codegen
8
public readonly partial struct U8String
9
{
10
    // TODO: Optimize/deduplicate Concat variants
11
    // TODO: Investigate if it is possible fold validation for u8 literals
12
    public static U8String Concat(U8String left, U8String right)
13
    {
14
        if (!left.IsEmpty)
×
15
        {
16
            if (!right.IsEmpty)
×
17
            {
18
                var length = left.Length + right.Length;
×
19
                var value = new byte[length];
×
20

21
                left.UnsafeSpan.CopyTo(value);
×
22
                right.UnsafeSpan.CopyTo(value.AsSpan(left.Length));
×
23

24
                return new U8String(value, 0, length);
×
25
            }
26

27
            return left;
×
28
        }
29

30
        return right;
×
31
    }
32

33
    public static U8String Concat(U8String left, ReadOnlySpan<byte> right)
34
    {
35
        if (!right.IsEmpty)
×
36
        {
37
            Validate(right);
×
38
            if (!left.IsEmpty)
×
39
            {
40
                var length = left.Length + right.Length;
×
41
                var value = new byte[length];
×
42

43
                left.UnsafeSpan.CopyTo(value);
×
44
                right.CopyTo(value.AsSpan(left.Length));
×
45

46
                return new U8String(value, 0, length);
×
47
            }
48

49
            return new U8String(right, skipValidation: true);
×
50
        }
51

52
        return left;
×
53
    }
54

55
    public static U8String Concat(ReadOnlySpan<byte> left, U8String right)
56
    {
57
        if (!left.IsEmpty)
×
58
        {
59
            Validate(left);
×
60
            if (!right.IsEmpty)
×
61
            {
62
                var length = left.Length + right.Length;
×
63
                var value = new byte[length];
×
64

65
                left.CopyTo(value);
×
66
                right.UnsafeSpan.CopyTo(value.AsSpan(left.Length));
×
67

68
                return new U8String(value, 0, length);
×
69
            }
70

71
            return new U8String(left, skipValidation: true);
×
72
        }
73

74
        return right;
×
75
    }
76

77
    public static U8String Concat(ReadOnlySpan<byte> left, ReadOnlySpan<byte> right)
78
    {
79
        var length = left.Length + right.Length;
×
80
        if (length != 0)
×
81
        {
82
            var value = new byte[length];
×
83

84
            left.CopyTo(value);
×
85
            right.CopyTo(value.SliceUnsafe(left.Length, right.Length));
×
86

87
            Validate(value);
×
88
            return new U8String(value, 0, length);
×
89
        }
90

91
        return default;
×
92
    }
93

94
    /// <summary>
95
    /// Normalizes current <see cref="U8String"/> to the specified Unicode normalization form (default: <see cref="NormalizationForm.FormC"/>).
96
    /// </summary>
97
    /// <returns>A new <see cref="U8String"/> normalized to the specified form.</returns>
98
    public U8String Normalize(NormalizationForm form = NormalizationForm.FormC)
99
    {
100
        throw new NotImplementedException();
×
101
    }
102

103
    public U8String Replace(byte oldValue, byte newValue)
104
    {
105
        var source = this;
×
106
        if (!source.IsEmpty)
×
107
        {
108
            var current = source.UnsafeSpan;
×
109
            var firstReplace = current.IndexOf(oldValue);
×
110
            if (firstReplace < 0)
×
111
            {
112
                return source;
×
113
            }
114

115
            var replaced = new byte[source.Length];
×
116
            var destination = replaced.AsSpan();
×
117

118
            current
×
119
                .SliceUnsafe(0, firstReplace)
×
120
                .CopyTo(destination);
×
121

122
            destination = destination.SliceUnsafe(firstReplace);
×
123
            current
×
124
                .SliceUnsafe(firstReplace)
×
125
                .Replace(destination, oldValue, newValue);
×
126

127
            // Old and new bytes which individually are invalid unicode scalar values
128
            // are allowed if the replacement produces a valid UTF-8 sequence.
129
            Validate(replaced);
×
130
            return new(replaced, 0, source.Length);
×
131
        }
132

133
        return default;
×
134
    }
135

136
    /// <inheritdoc />
137
    public void CopyTo(byte[] destination, int index)
138
    {
139
        var src = this;
×
140
        var dst = destination.AsSpan()[index..];
×
141
        if (src.Length > dst.Length)
×
142
        {
143
            ThrowHelpers.ArgumentOutOfRange(nameof(index));
×
144
        }
145

146
        src.UnsafeSpan.CopyTo(dst);
×
147
    }
×
148

149
    /// <summary>
150
    /// Retrieves a substring from this instance. The substring starts at a specified
151
    /// character position and continues to the end of the string.
152
    /// </summary>
153
    /// <param name="start">The zero-based starting character position of a substring in this instance.</param>
154
    /// <returns>A substring view that begins at <paramref name="start"/>.</returns>
155
    /// <exception cref="ArgumentOutOfRangeException">
156
    /// <paramref name="start"/> is less than zero or greater than the length of this instance.
157
    /// </exception>
158
    /// <exception cref="ArgumentException">
159
    /// The resulting substring splits at a UTF-8 code point boundary and would result in an invalid UTF-8 string.
160
    /// </exception>
161
    public U8String Slice(int start)
162
    {
163
        var source = this;
×
164
        // From ReadOnly/Span<T> Slice(int) implementation
165
        if ((ulong)(uint)start > (ulong)(uint)source.Length)
×
166
        {
167
            ThrowHelpers.ArgumentOutOfRange();
×
168
        }
169

170
        var length = source.Length - start;
×
171
        if (length > 0)
×
172
        {
173
            if (U8Info.IsContinuationByte(in source.UnsafeRefAdd(start)))
×
174
            {
175
                ThrowHelpers.InvalidSplit();
×
176
            }
177

178
            return new(source._value, source.Offset + start, length);
×
179
        }
180

181
        return default;
×
182
    }
183

184
    /// <summary>
185
    /// Retrieves a substring from this instance. The substring starts at a specified
186
    /// character position and has a specified length.
187
    /// </summary>
188
    /// <param name="start">The zero-based starting character position of a substring in this instance.</param>
189
    /// <param name="length">The number of bytes in the substring.</param>
190
    /// <returns>A substring view that begins at <paramref name="start"/> and has <paramref name="length"/> bytes.</returns>
191
    /// <exception cref="ArgumentOutOfRangeException">
192
    /// <paramref name="start"/> or <paramref name="length"/> is less than zero, or the sum of <paramref name="start"/> and <paramref name="length"/> is greater than the length of the current instance.
193
    /// </exception>
194
    /// <exception cref="ArgumentException">
195
    /// The resulting substring splits at a UTF-8 code point boundary and would result in an invalid UTF-8 string.
196
    /// </exception>
197
    public U8String Slice(int start, int length)
198
    {
199
        var source = this;
×
200
        // From ReadOnly/Span<T> Slice(int, int) implementation
201
        if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)source.Length)
×
202
        {
203
            ThrowHelpers.ArgumentOutOfRange();
×
204
        }
205

206
        var result = default(U8String);
×
207
        if (length > 0)
×
208
        {
209
            // TODO: Is there really no way to get rid of length < source.Length when checking the last+1 byte?
210
            if ((start > 0 && U8Info.IsContinuationByte(source.UnsafeRefAdd(start))) || (
×
211
                length < source.Length && U8Info.IsContinuationByte(source.UnsafeRefAdd(start + length))))
×
212
            {
213
                // TODO: Exception message UX
214
                ThrowHelpers.InvalidSplit();
×
215
            }
216

217
            result = new(source._value, source.Offset + start, length);
×
218
        }
219

220
        return result;
×
221
    }
222

223
    /// <summary>
224
    /// Removes all leading and trailing ASCII white-space characters from the current string.
225
    /// </summary>
226
    /// <returns>
227
    /// A substring that remains after all ASCII white-space characters
228
    /// are removed from the start and end of the current string.
229
    /// </returns>
230
    public U8String TrimAscii()
231
    {
232
        var source = this;
×
233
        var range = Ascii.Trim(source);
×
234

235
        return !range.IsEmpty()
×
236
            ? U8Marshal.Slice(source, range)
×
237
            : default;
×
238
    }
239

240
    /// <summary>
241
    /// Removes all the leading ASCII white-space characters from the current string.
242
    /// </summary>
243
    /// <returns>
244
    /// A substring that remains after all white-space characters
245
    /// are removed from the start of the current string.
246
    /// </returns>
247
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
248
    public U8String TrimStartAscii()
249
    {
250
        var source = this;
×
251
        var range = Ascii.TrimStart(source);
×
252

253
        return !range.IsEmpty()
×
254
            ? U8Marshal.Slice(source, range)
×
255
            : default;
×
256
    }
257

258
    /// <summary>
259
    /// Removes all the trailing ASCII white-space characters from the current string.
260
    /// </summary>
261
    /// <returns>
262
    /// A substring that remains after all white-space characters
263
    /// are removed from the end of the current string.
264
    /// </returns>
265
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
266
    public U8String TrimEndAscii()
267
    {
268
        var source = this;
×
269
        var range = Ascii.TrimEnd(source);
×
270

271
        return !range.IsEmpty()
×
272
            ? U8Marshal.Slice(source, range)
×
273
            : default;
×
274
    }
275

276
    /// <summary>
277
    /// Returns a copy of this ASCII string converted to lower case.
278
    /// </summary>
279
    /// <returns>A lowercase equivalent of the current ASCII string.</returns>
280
    /// <exception cref="ArgumentException">
281
    /// The current string is not a valid ASCII sequence.
282
    /// </exception>
283
    public U8String ToLowerAscii()
284
    {
285
        var source = this;
×
286
        if (!source.IsEmpty)
×
287
        {
288
            var span = source.UnsafeSpan;
×
289
            var destination = new byte[span.Length];
×
290
            var result = Ascii.ToLower(span, destination, out _);
×
291
            if (result is OperationStatus.InvalidData)
×
292
            {
293
                ThrowHelpers.InvalidAscii();
×
294
            }
295

296
            return new U8String(destination, 0, span.Length);
×
297
        }
298

299
        return default;
×
300
    }
301

302
    /// <summary>
303
    /// Returns a copy of this ASCII string converted to upper case.
304
    /// </summary>
305
    /// <returns>The uppercase equivalent of the current ASCII string.</returns>
306
    /// <exception cref="ArgumentException">
307
    /// The current string is not a valid ASCII sequence.
308
    /// </exception>
309
    public U8String ToUpperAscii()
310
    {
311
        var source = this;
×
312
        if (!source.IsEmpty)
×
313
        {
314
            var span = source.UnsafeSpan;
×
315
            var destination = new byte[span.Length];
×
316
            var result = Ascii.ToUpper(span, destination, out _);
×
317
            if (result is OperationStatus.InvalidData)
×
318
            {
319
                ThrowHelpers.InvalidAscii();
×
320
            }
321

322
            return new U8String(destination, 0, span.Length);
×
323
        }
324

325
        return default;
×
326
    }
327
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc