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

neon-sunset / U8String / 5945937843

23 Aug 2023 01:33AM UTC coverage: 24.028% (-0.1%) from 24.144%
5945937843

push

github

neon-sunset
refactor: simplify U8Searching and speed-up ref split enumerator

151 of 814 branches covered (18.55%)

Branch coverage included in aggregate %.

28 of 28 new or added lines in 2 files covered. (100.0%)

498 of 1887 relevant lines covered (26.39%)

43687.73 hits per line

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

0.0
/src/U8String.Splitting.cs
1
using System.Collections;
2
using System.Runtime.InteropServices;
3
using System.Text;
4
using U8Primitives.Abstractions;
5
using U8Primitives.InteropServices;
6

7
#pragma warning disable RCS1085, RCS1085FadeOut, IDE0032 // Use auto-implemented property. Why: readable fields.
8
namespace U8Primitives;
9

10
public readonly partial struct U8String
11
{
12
    public U8SplitPair SplitFirst(byte separator)
13
    {
14
        if (!U8Info.IsAsciiByte(separator))
×
15
        {
16
            // TODO: EH UX
17
            ThrowHelpers.ArgumentOutOfRange();
×
18
        }
19

20
        var source = this;
×
21
        if (!source.IsEmpty)
×
22
        {
23
            var span = source.UnsafeSpan;
×
24
            var index = span.IndexOf(separator);
×
25
            if (index >= 0)
×
26
            {
27
                return new(source, index, 1);
×
28
            }
29

30
            return U8SplitPair.NotFound(source);
×
31
        }
32

33
        return default;
×
34
    }
35

36
    public U8SplitPair SplitFirst(char separator) => char.IsAscii(separator)
×
37
        ? SplitFirst((byte)separator)
×
38
        : SplitFirstUnchecked(U8Scalar.Create(separator, checkAscii: false).AsSpan());
×
39

40
    public U8SplitPair SplitFirst(Rune separator) => separator.IsAscii
×
41
        ? SplitFirst((byte)separator.Value)
×
42
        : SplitFirstUnchecked(U8Scalar.Create(separator, checkAscii: false).AsSpan());
×
43

44
    public U8SplitPair SplitFirst(U8String separator)
45
    {
46
        var source = this;
×
47
        if (!source.IsEmpty)
×
48
        {
49
            if (!separator.IsEmpty)
×
50
            {
51
                var span = source.UnsafeSpan;
×
52
                var index = span.IndexOf(separator.UnsafeSpan);
×
53
                if (index >= 0)
×
54
                {
55
                    return new(source, index, separator.Length);
×
56
                }
57
            }
58

59
            return U8SplitPair.NotFound(source);
×
60
        }
61

62
        return default;
×
63
    }
64

65
    // It would be *really nice* to aggressively inline this method
66
    // but the way validation is currently implemented does not significantly
67
    // benefit from splitting on UTF-8 literals while possibly risking
68
    // running out of inlining budget significantly regressing performance everywhere else.
69
    public U8SplitPair SplitFirst(ReadOnlySpan<byte> separator)
70
    {
71
        var source = this;
×
72
        if (!source.IsEmpty)
×
73
        {
74
            if (!separator.IsEmpty)
×
75
            {
76
                var span = source.UnsafeSpan;
×
77
                var index = span.IndexOf(separator);
×
78
                if (index >= 0)
×
79
                {
80
                    // Same as with Slice(int, int), this might dereference past the end of the string.
81
                    // TODO: Do something about it if it's ever an issue.
82
                    if (U8Info.IsContinuationByte(source.UnsafeRefAdd(index)) ||
×
83
                        U8Info.IsContinuationByte(source.UnsafeRefAdd(index + separator.Length)))
×
84
                    {
85
                        ThrowHelpers.InvalidSplit();
×
86
                    }
87

88
                    return new(source, index, separator.Length);
×
89
                }
90
            }
91

92
            return U8SplitPair.NotFound(source);
×
93
        }
94

95
        return default;
×
96
    }
97

98
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
99
    public U8SplitPair SplitFirstUnchecked(ReadOnlySpan<byte> separator)
100
    {
101
        var source = this;
×
102
        if (!source.IsEmpty)
×
103
        {
104
            if (!separator.IsEmpty)
×
105
            {
106
                var span = source.UnsafeSpan;
×
107
                var index = span.IndexOf(separator);
×
108
                if (index >= 0)
×
109
                {
110
                    return new(source, index, separator.Length);
×
111
                }
112
            }
113

114
            return U8SplitPair.NotFound(source);
×
115
        }
116

117
        return default;
×
118
    }
119

120
    public U8SplitPair SplitLast(byte separator)
121
    {
122
        if (!U8Info.IsAsciiByte(separator))
×
123
        {
124
            // TODO: EH UX
125
            ThrowHelpers.ArgumentOutOfRange();
×
126
        }
127

128
        var source = this;
×
129
        if (!source.IsEmpty)
×
130
        {
131
            var span = source.UnsafeSpan;
×
132
            var index = span.LastIndexOf(separator);
×
133
            if (index >= 0)
×
134
            {
135
                return new(source, index, 1);
×
136
            }
137

138
            return U8SplitPair.NotFound(source);
×
139
        }
140

141
        return default;
×
142
    }
143

144
    public U8SplitPair SplitLast(char separator) => char.IsAscii(separator)
×
145
        ? SplitLast((byte)separator)
×
146
        : SplitLastUnchecked(U8Scalar.Create(separator, checkAscii: false).AsSpan());
×
147

148
    public U8SplitPair SplitLast(Rune separator) => separator.IsAscii
×
149
        ? SplitLast((byte)separator.Value)
×
150
        : SplitLastUnchecked(U8Scalar.Create(separator, checkAscii: false).AsSpan());
×
151

152
    public U8SplitPair SplitLast(U8String separator)
153
    {
154
        var source = this;
×
155
        if (!source.IsEmpty)
×
156
        {
157
            if (!separator.IsEmpty)
×
158
            {
159
                var span = source.UnsafeSpan;
×
160
                var index = span.LastIndexOf(separator.UnsafeSpan);
×
161
                if (index >= 0)
×
162
                {
163
                    return new(source, index, separator.Length);
×
164
                }
165
            }
166

167
            return U8SplitPair.NotFound(source);
×
168
        }
169

170
        return default;
×
171
    }
172

173
    public U8SplitPair SplitLast(ReadOnlySpan<byte> separator)
174
    {
175
        var source = this;
×
176
        if (!source.IsEmpty)
×
177
        {
178
            if (!separator.IsEmpty)
×
179
            {
180
                var span = source.UnsafeSpan;
×
181
                var index = span.LastIndexOf(separator);
×
182
                if (index >= 0)
×
183
                {
184
                    if (U8Info.IsContinuationByte(source.UnsafeRefAdd(index)) ||
×
185
                        U8Info.IsContinuationByte(source.UnsafeRefAdd(index + separator.Length)))
×
186
                    {
187
                        ThrowHelpers.InvalidSplit();
×
188
                    }
189

190
                    return new(source, index, separator.Length);
×
191
                }
192
            }
193

194
            return U8SplitPair.NotFound(source);
×
195
        }
196

197
        return default;
×
198
    }
199

200
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
201
    public U8SplitPair SplitLastUnchecked(ReadOnlySpan<byte> separator)
202
    {
203
        var source = this;
×
204
        if (!source.IsEmpty)
×
205
        {
206
            if (!separator.IsEmpty)
×
207
            {
208
                var span = source.UnsafeSpan;
×
209
                var index = span.LastIndexOf(separator);
×
210
                if (index >= 0)
×
211
                {
212
                    return new(source, index, separator.Length);
×
213
                }
214
            }
215

216
            return U8SplitPair.NotFound(source);
×
217
        }
218

219
        return default;
×
220
    }
221

222
    public U8Split<byte> Split(byte separator)
223
    {
224
        if (!U8Info.IsAsciiByte(separator))
×
225
        {
226
            ThrowHelpers.ArgumentOutOfRange();
×
227
        }
228

229
        return new(this, separator);
×
230
    }
231

232
    public U8Split<char> Split(char separator)
233
    {
234
        if (char.IsSurrogate(separator))
×
235
        {
236
            ThrowHelpers.ArgumentOutOfRange();
×
237
        }
238

239
        return new(this, separator);
×
240
    }
241

242
    public U8Split<Rune> Split(Rune separator) => new(this, separator);
×
243

244
    public U8Split Split(U8String separator)
245
    {
246
        return new(this, separator);
×
247
    }
248

249
    public U8RefSplit Split(ReadOnlySpan<byte> separator)
250
    {
251
        if (!IsValid(separator))
×
252
        {
253
            ThrowHelpers.InvalidSplit();
×
254
        }
255

256
        return new(this, separator);
×
257
    }
258

259
    public ConfiguredU8Split<byte> Split(byte separator, U8SplitOptions options)
260
    {
261
        if (!U8Info.IsAsciiByte(separator))
×
262
        {
263
            ThrowHelpers.ArgumentOutOfRange();
×
264
        }
265

266
        return new(this, separator, options);
×
267
    }
268

269
    public ConfiguredU8Split<char> Split(char separator, U8SplitOptions options)
270
    {
271
        if (char.IsSurrogate(separator))
×
272
        {
273
            ThrowHelpers.ArgumentOutOfRange();
×
274
        }
275

276
        return new(this, separator, options);
×
277
    }
278

279
    public ConfiguredU8Split<Rune> Split(Rune separator, U8SplitOptions options) => new(this, separator, options);
×
280

281
    public ConfiguredU8Split Split(U8String separator, U8SplitOptions options)
282
    {
283
        return new(this, separator, options);
×
284
    }
285
}
286

287
public readonly record struct U8SplitPair
288
{
289
    readonly U8String _value;
290
    readonly int _offset;
291
    readonly int _stride;
292

293
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
294
    internal U8SplitPair(U8String value, int offset, int stride)
295
    {
296
        _value = value;
×
297
        _offset = offset;
×
298
        _stride = stride;
×
299
    }
×
300

301
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
302
    public static U8SplitPair NotFound(U8String value)
303
    {
304
        return new(value, value.Length, 0);
×
305
    }
306

307
    public U8String Segment
308
    {
309
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
310
        get => U8Marshal.Slice(_value, 0, _offset);
×
311
    }
312

313
    public U8String Remainder
314
    {
315
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
316
        get => U8Marshal.Slice(_value, _offset + _stride);
×
317
    }
318

319
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
320
    public void Deconstruct(out U8String segment, out U8String remainder)
321
    {
322
        segment = Segment;
×
323
        remainder = Remainder;
×
324
    }
×
325

326
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
327
    public static implicit operator (U8String, U8String)(U8SplitPair value)
328
    {
329
        return (value.Segment, value.Remainder);
×
330
    }
331
}
332

333
public struct U8Split : ICollection<U8String>, IU8Enumerable<U8Split.Enumerator>
334
{
335
    readonly U8String _value;
336
    readonly U8String _separator;
337
    int _count;
338

339
    public U8Split(U8String value, U8String separator)
340
    {
341
        _value = value;
×
342
        _separator = separator;
×
343
        _count = value.IsEmpty ? 0 : -1;
×
344
    }
×
345

346
    public int Count
347
    {
348
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
349
        get
350
        {
351
            var count = _count;
×
352
            if (count >= 0)
×
353
            {
354
                return count;
×
355
            }
356

357
            // Matches the behavior of string.Split('\n').Length for "hello\n"
358
            // TODO: Should we break consistency and not count the very last segment if it is empty?
359
            return _count = Count(_value.UnsafeSpan, _separator) + 1;
×
360

361
            static int Count(ReadOnlySpan<byte> value, ReadOnlySpan<byte> separator)
362
            {
363
                return U8Searching.Count(value, separator);
×
364
            }
365
        }
366
    }
367

368
    public readonly bool Contains(U8String item)
369
    {
370
        return U8Searching.SplitContains(_value, _separator, item);
×
371
    }
372

373
    public void CopyTo(U8String[] array, int index)
374
    {
375
        this.CopyTo<U8Split, Enumerator, U8String>(array.AsSpan()[index..]);
×
376
    }
×
377

378
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
379
    public readonly void Deconstruct(out U8String first, out U8String second)
380
    {
381
        this.Deconstruct<U8Split, Enumerator, U8String>(out first, out second);
×
382
    }
×
383

384
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
385
    public readonly void Deconstruct(out U8String first, out U8String second, out U8String third)
386
    {
387
        this.Deconstruct<U8Split, Enumerator, U8String>(out first, out second, out third);
×
388
    }
×
389

390
    public U8String[] ToArray() => this.ToArray<U8Split, Enumerator, U8String>();
×
391
    public List<U8String> ToList() => this.ToList<U8Split, Enumerator, U8String>();
×
392

393
    /// <summary>
394
    /// Returns a <see cref="Enumerator"/> over the provided string.
395
    /// </summary>
396
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
397
    public readonly Enumerator GetEnumerator() => new(_value, _separator);
×
398

399
    readonly IEnumerator<U8String> IEnumerable<U8String>.GetEnumerator() => GetEnumerator();
×
400
    readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
×
401
    readonly bool ICollection<U8String>.IsReadOnly => true;
×
402

403
    public struct Enumerator : IU8Enumerator
404
    {
405
        readonly byte[]? _value;
406
        readonly U8String _separator;
407
        U8Range _current;
408
        U8Range _remaining;
409

410
        internal Enumerator(U8String value, U8String separator)
411
        {
412
            _value = value._value;
×
413
            _separator = separator;
×
414
            _remaining = value._inner;
×
415
        }
×
416

417
        public readonly U8String Current => new(_value, _current.Offset, _current.Length);
×
418

419
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
420
        public bool MoveNext()
421
        {
422
            var remaining = _remaining;
×
423
            if (remaining.Length > 0)
×
424
            {
425
                var value = _value!.SliceUnsafe(remaining.Offset, remaining.Length);
×
426
                var separator = _separator;
×
427
                var index = value.IndexOf(separator);
×
428
                if (index >= 0)
×
429
                {
430
                    _current = new(remaining.Offset, index);
×
431
                    _remaining = new(
×
432
                        remaining.Offset + index + separator.Length,
×
433
                        remaining.Length - index - separator.Length);
×
434
                }
435
                else
436
                {
437
                    _current = remaining;
×
438
                    _remaining = default;
×
439
                }
440

441
                return true;
×
442
            }
443

444
            return false;
×
445
        }
446

447
        readonly object IEnumerator.Current => Current;
×
448
        readonly void IEnumerator.Reset() => throw new NotSupportedException();
×
449
        readonly void IDisposable.Dispose() { }
×
450
    }
451

452
    readonly void ICollection<U8String>.Add(U8String item) => throw new NotSupportedException();
×
453
    readonly void ICollection<U8String>.Clear() => throw new NotSupportedException();
×
454
    readonly bool ICollection<U8String>.Remove(U8String item) => throw new NotSupportedException();
×
455
}
456

457
// TODO: Optimize even more. This design is far from the northstar of perfect codegen
458
// but it still somehow manages to outperform Rust split iterators
459
public struct U8Split<TSeparator> :
460
    ICollection<U8String>,
461
    IU8Enumerable<U8Split<TSeparator>.Enumerator>
462
        where TSeparator : unmanaged
463
{
464
    readonly U8String _value;
465
    readonly TSeparator _separator;
466
    int _count;
467

468
    internal U8Split(U8String value, TSeparator separator)
469
    {
470
        _value = value;
×
471
        _separator = separator;
×
472
        _count = value.IsEmpty ? 0 : -1;
×
473
    }
×
474

475
    public int Count
476
    {
477
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
478
        get
479
        {
480
            var count = _count;
×
481
            if (count >= 0)
×
482
            {
483
                return count;
×
484
            }
485

486
            // Matches the behavior of string.Split('\n').Length for "hello\n"
487
            // TODO: Should we break consistency and not count the very last segment if it is empty?
488
            return _count = Count(_value, _separator) + 1;
×
489

490
            static int Count(U8String value, TSeparator separator)
491
            {
492
                return U8Searching.Count(value, separator);
×
493
            }
494
        }
495
    }
496

497
    public readonly bool Contains(U8String item)
498
    {
499
        return U8Searching.SplitContains(_value, _separator, item);
×
500
    }
501

502
    public void CopyTo(U8String[] array, int index)
503
    {
504
        this.CopyTo<U8Split<TSeparator>, Enumerator, U8String>(array.AsSpan()[index..]);
×
505
    }
×
506

507
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
508
    public readonly void Deconstruct(out U8String first, out U8String second)
509
    {
510
        this.Deconstruct<U8Split<TSeparator>, Enumerator, U8String>(out first, out second);
×
511
    }
×
512

513
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
514
    public readonly void Deconstruct(out U8String first, out U8String second, out U8String third)
515
    {
516
        this.Deconstruct<U8Split<TSeparator>, Enumerator, U8String>(out first, out second, out third);
×
517
    }
×
518

519
    public U8String[] ToArray() => this.ToArray<U8Split<TSeparator>, Enumerator, U8String>();
×
520
    public List<U8String> ToList() => this.ToList<U8Split<TSeparator>, Enumerator, U8String>();
×
521

522
    /// <summary>
523
    /// Returns a <see cref="Enumerator"/> over the provided string.
524
    /// </summary>
525
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
526
    public readonly Enumerator GetEnumerator() => new(_value, _separator);
×
527

528
    readonly IEnumerator<U8String> IEnumerable<U8String>.GetEnumerator() => GetEnumerator();
×
529
    readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
×
530
    readonly bool ICollection<U8String>.IsReadOnly => true;
×
531

532
    public struct Enumerator : IU8Enumerator
533
    {
534
        readonly byte[]? _value;
535
        readonly TSeparator _separator;
536
        U8Range _current;
537
        U8Range _remaining;
538

539
        internal Enumerator(U8String value, TSeparator separator)
540
        {
541
            _value = value._value;
×
542
            _separator = separator;
×
543
            _remaining = value._inner;
×
544
        }
×
545

546
        public readonly U8String Current => new(_value, _current.Offset, _current.Length);
×
547

548
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
549
        public bool MoveNext()
550
        {
551
            var remaining = _remaining;
×
552
            if (remaining.Length > 0)
×
553
            {
554
                var value = _value!.SliceUnsafe(remaining.Offset, remaining.Length);
×
555
                var index = U8Searching.IndexOf(value, _separator, out var size);
×
556
                if (index >= 0)
×
557
                {
558
                    _current = new(remaining.Offset, index);
×
559
                    _remaining = new(
×
560
                        remaining.Offset + index + size,
×
561
                        remaining.Length - index - size);
×
562
                }
563
                else
564
                {
565
                    _current = remaining;
×
566
                    _remaining = default;
×
567
                }
568

569
                return true;
×
570
            }
571

572
            return false;
×
573
        }
574

575
        readonly object IEnumerator.Current => Current;
×
576
        readonly void IEnumerator.Reset() => throw new NotSupportedException();
×
577
        readonly void IDisposable.Dispose() { }
×
578
    }
579

580
    readonly void ICollection<U8String>.Add(U8String item) => throw new NotSupportedException();
×
581
    readonly void ICollection<U8String>.Clear() => throw new NotSupportedException();
×
582
    readonly bool ICollection<U8String>.Remove(U8String item) => throw new NotSupportedException();
×
583
}
584

585
public readonly struct ConfiguredU8Split :
586
    IU8Enumerable<ConfiguredU8Split.Enumerator>
587
{
588
    readonly U8String _value;
589
    readonly U8String _separator;
590
    readonly U8SplitOptions _options;
591

592
    public ConfiguredU8Split(U8String value, U8String separator, U8SplitOptions options)
593
    {
594
        _value = value;
×
595
        _separator = separator;
×
596
        _options = options;
×
597
    }
×
598

599
    public readonly Enumerator GetEnumerator() => new(_value, _separator, _options);
×
600

601
    readonly IEnumerator<U8String> IEnumerable<U8String>.GetEnumerator() => GetEnumerator();
×
602
    readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
×
603

604
    public struct Enumerator : IU8Enumerator
605
    {
606
        readonly byte[]? _value;
607
        readonly U8String _separator;
608
        readonly U8SplitOptions _options;
609
        U8Range _current;
610
        U8Range _remaining;
611

612
        public Enumerator(U8String value, U8String separator, U8SplitOptions options)
613
        {
614
            _value = value._value;
×
615
            _separator = separator;
×
616
            _options = options;
×
617
            _remaining = value._inner;
×
618
        }
×
619

620
        public readonly U8String Current => new(_value, _current.Offset, _current.Length);
×
621

622
        // TODO: Not most efficient but it works for now
623
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
624
        public bool MoveNext()
625
        {
626
        Next:
627
            var remaining = _remaining;
×
628
            if (remaining.Length > 0)
×
629
            {
630
                var value = _value!.SliceUnsafe(remaining.Offset, remaining.Length);
×
631
                var separator = _separator;
×
632
                var index = value.IndexOf(separator);
×
633
                if (index >= 0)
×
634
                {
635
                    _current = (_options & U8SplitOptions.Trim) != U8SplitOptions.Trim
×
636
                        ? new(remaining.Offset, index)
×
637
                        : TrimEntry(_value!, new(remaining.Offset, index));
×
638
                    _remaining = new(
×
639
                        remaining.Offset + index + separator.Length,
×
640
                        remaining.Length - index - separator.Length);
×
641
                }
642
                else
643
                {
644
                    _current = (_options & U8SplitOptions.Trim) != U8SplitOptions.Trim
×
645
                        ? remaining
×
646
                        : TrimEntry(_value!, remaining);
×
647
                    _remaining = default;
×
648
                }
649

650
                if ((_options & U8SplitOptions.RemoveEmpty) is U8SplitOptions.RemoveEmpty
×
651
                    && _current.Length is 0)
×
652
                {
653
                    goto Next;
654
                }
655

656
                return true;
×
657
            }
658

659
            return false;
×
660
        }
661

662
        private static U8Range TrimEntry(byte[] value, U8Range range)
663
        {
664
            // This could have been done better but works for now.
665
            return new U8String(value, range).Trim()._inner;
×
666
        }
667

668
        readonly object IEnumerator.Current => Current;
×
669
        readonly void IEnumerator.Reset() => throw new NotSupportedException();
×
670
        readonly void IDisposable.Dispose() { }
×
671
    }
672
}
673

674
public readonly struct ConfiguredU8Split<TSeparator> :
675
    IU8Enumerable<ConfiguredU8Split<TSeparator>.Enumerator>
676
        where TSeparator : unmanaged
677
{
678
    readonly U8String _value;
679
    readonly TSeparator _separator;
680
    readonly U8SplitOptions _options;
681

682
    internal ConfiguredU8Split(U8String value, TSeparator separator, U8SplitOptions options)
683
    {
684
        _value = value;
×
685
        _separator = separator;
×
686
        _options = options;
×
687
    }
×
688

689
    public readonly Enumerator GetEnumerator() => new(_value, _separator, _options);
×
690

691
    readonly IEnumerator<U8String> IEnumerable<U8String>.GetEnumerator() => GetEnumerator();
×
692
    readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
×
693

694
    public struct Enumerator : IU8Enumerator
695
    {
696
        readonly byte[]? _value;
697
        readonly TSeparator _separator;
698
        readonly U8SplitOptions _options;
699
        U8Range _current;
700
        U8Range _remaining;
701

702
        internal Enumerator(U8String value, TSeparator separator, U8SplitOptions options)
703
        {
704
            _value = value._value;
×
705
            _separator = separator;
×
706
            _options = options;
×
707
            _remaining = value._inner;
×
708
        }
×
709

710
        public readonly U8String Current => new(_value, _current.Offset, _current.Length);
×
711

712
        // TODO: Not most efficient but it works for now
713
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
714
        public bool MoveNext()
715
        {
716
        Next:
717
            var remaining = _remaining;
×
718
            if (remaining.Length > 0)
×
719
            {
720
                var value = _value!.SliceUnsafe(remaining.Offset, remaining.Length);
×
721
                var index = U8Searching.IndexOf(value, _separator, out var size);
×
722
                if (index >= 0)
×
723
                {
724
                    _current = (_options & U8SplitOptions.Trim) != U8SplitOptions.Trim
×
725
                        ? new(remaining.Offset, index)
×
726
                        : TrimEntry(_value!, new(remaining.Offset, index));
×
727
                    _remaining = new(
×
728
                        remaining.Offset + index + size,
×
729
                        remaining.Length - index - size);
×
730
                }
731
                else
732
                {
733
                    _current = (_options & U8SplitOptions.Trim) != U8SplitOptions.Trim
×
734
                        ? remaining
×
735
                        : TrimEntry(_value!, remaining);
×
736
                    _remaining = default;
×
737
                }
738

739
                if ((_options & U8SplitOptions.RemoveEmpty) is U8SplitOptions.RemoveEmpty
×
740
                    && _current.Length is 0)
×
741
                {
742
                    goto Next;
743
                }
744

745
                return true;
×
746
            }
747

748
            return false;
×
749
        }
750

751
        private static U8Range TrimEntry(byte[] value, U8Range range)
752
        {
753
            // This could have been done better but works for now.
754
            return new U8String(value, range).Trim()._inner;
×
755
        }
756

757
        readonly object IEnumerator.Current => Current;
×
758
        readonly void IEnumerator.Reset() => throw new NotSupportedException();
×
759
        readonly void IDisposable.Dispose() { }
×
760
    }
761
}
762

763
// Unfortunately, because ref structs cannot be used as generic type arguments,
764
// we have to resort to duplicating the implementation and exposing methods manually.
765
// What's worse, ConfiguredU8RefSplit has to be duplicated as well. Things we do for performance...
766
// TODO: Implement this
767
// TODO 2: Eventually, remove duplicate code once ref structs can be used as generics.
768
public readonly ref struct U8RefSplit
769
{
770
    readonly U8String _value;
771
    readonly ReadOnlySpan<byte> _separator;
772

773
    public U8RefSplit(U8String value, ReadOnlySpan<byte> separator)
774
    {
775
        _value = value;
×
776
        _separator = separator;
×
777
    }
×
778

779
    public bool Contains(U8String item)
780
    {
781
        return U8Searching.SplitContains(_value, _separator, item);
×
782
    }
783

784
    public void CopyTo(U8String[] array, int index)
785
    {
786
        var span = array.AsSpan()[index..];
×
787
        var split = this;
×
788
        if (split._value.Length > 0)
×
789
        {
790
            var count = U8Searching.Count(
×
791
                split._value.UnsafeSpan,
×
792
                split._separator) + 1;
×
793
            span = span[..count];
×
794

795
            var i = 0;
×
796
            ref var dst = ref span.AsRef();
×
797
            foreach (var item in split)
×
798
            {
799
                dst.Add(i++) = item;
×
800
            }
801
        }
802
    }
×
803

804
    public U8String[] ToArray()
805
    {
806
        var split = this;
×
807
        if (split._value.Length > 0)
×
808
        {
809
            var count = U8Searching.Count(
×
810
                split._value.UnsafeSpan,
×
811
                split._separator) + 1;
×
812

813
            var result = new U8String[count];
×
814
            var span = result.AsSpan();
×
815

816
            var i = 0;
×
817
            ref var dst = ref span.AsRef();
×
818
            foreach (var item in split)
×
819
            {
820
                dst.Add(i++) = item;
×
821
            }
822

823
            return result;
×
824
        }
825

826
        return Array.Empty<U8String>();
×
827
    }
828

829
    public List<U8String> ToList()
830
    {
831
        var split = this;
×
832
        if (split._value.Length > 0)
×
833
        {
834
            var count = U8Searching.Count(
×
835
                split._value.UnsafeSpan,
×
836
                split._separator) + 1;
×
837

838
            var result = new List<U8String>(count);
×
839
            CollectionsMarshal.SetCount(result, count);
×
840
            var span = CollectionsMarshal.AsSpan(result);
×
841

842
            var i = 0;
×
843
            ref var dst = ref span.AsRef();
×
844
            foreach (var item in split)
×
845
            {
846
                dst.Add(i++) = item;
×
847
            }
848

849
            return result;
×
850
        }
851

852
        return new List<U8String>();
×
853
    }
854

855
    public readonly Enumerator GetEnumerator() => new(_value, _separator);
×
856

857
    public ref struct Enumerator
858
    {
859
        readonly byte[]? _value;
860
        readonly ReadOnlySpan<byte> _separator;
861
        U8Range _current;
862
        U8Range _remaining;
863

864
        internal Enumerator(U8String value, ReadOnlySpan<byte> separator)
865
        {
866
            _value = value._value;
×
867
            _separator = separator;
×
868
            _remaining = value._inner;
×
869
        }
×
870

871
        public readonly U8String Current
872
        {
873
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
874
            get => new(_value, _current.Offset, _current.Length);
×
875
        }
876

877
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
878
        public bool MoveNext()
879
        {
880
            var remaining = _remaining;
×
881
            if (remaining.Length > 0)
×
882
            {
883
                var value = _value!.SliceUnsafe(remaining.Offset, remaining.Length);
×
884
                var separator = _separator;
×
885
                var index = U8Searching.IndexOf(value, separator);
×
886
                if (index >= 0)
×
887
                {
888
                    _current = new(remaining.Offset, index);
×
889
                    _remaining = new(
×
890
                        remaining.Offset + index + separator.Length,
×
891
                        remaining.Length - index - separator.Length);
×
892
                }
893
                else
894
                {
895
                    _current = remaining;
×
896
                    _remaining = default;
×
897
                }
898

899
                return true;
×
900
            }
901

902
            return false;
×
903
        }
904
    }
905
}
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