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

neon-sunset / U8String / 5959247322

24 Aug 2023 03:52AM UTC coverage: 19.8%. Remained the same
5959247322

push

github

neon-sunset
fix: make ref split ctor internal, improve split count codegen

133 of 960 branches covered (13.85%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

481 of 2141 relevant lines covered (22.47%)

38499.93 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
    public readonly void Deconstruct(out U8String first, out U8String second)
379
    {
380
        this.Deconstruct<U8Split, Enumerator, U8String>(out first, out second);
×
381
    }
×
382

383
    public readonly void Deconstruct(out U8String first, out U8String second, out U8String third)
384
    {
385
        this.Deconstruct<U8Split, Enumerator, U8String>(out first, out second, out third);
×
386
    }
×
387

388
    public readonly U8String ElementAt(int index)
389
    {
390
        return this.ElementAt<U8Split, Enumerator, U8String>(index);
×
391
    }
392

393
    public readonly U8String ElementAtOrDefault(int index)
394
    {
395
        return this.ElementAtOrDefault<U8Split, Enumerator, U8String>(index);
×
396
    }
397

398
    public U8String[] ToArray() => this.ToArray<U8Split, Enumerator, U8String>();
×
399
    public List<U8String> ToList() => this.ToList<U8Split, Enumerator, U8String>();
×
400

401
    /// <summary>
402
    /// Returns a <see cref="Enumerator"/> over the provided string.
403
    /// </summary>
404
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
405
    public readonly Enumerator GetEnumerator() => new(_value, _separator);
×
406

407
    readonly IEnumerator<U8String> IEnumerable<U8String>.GetEnumerator() => GetEnumerator();
×
408
    readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
×
409
    readonly bool ICollection<U8String>.IsReadOnly => true;
×
410

411
    public struct Enumerator : IU8Enumerator
412
    {
413
        readonly byte[]? _value;
414
        readonly U8String _separator;
415
        U8Range _current;
416
        U8Range _remaining;
417

418
        internal Enumerator(U8String value, U8String separator)
419
        {
420
            _value = value._value;
×
421
            _separator = separator;
×
422
            _remaining = value._inner;
×
423
        }
×
424

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

427
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
428
        public bool MoveNext()
429
        {
430
            var remaining = _remaining;
×
431
            if (remaining.Length > 0)
×
432
            {
433
                var value = _value!.SliceUnsafe(remaining.Offset, remaining.Length);
×
434
                var separator = _separator;
×
435
                var index = value.IndexOf(separator);
×
436
                if (index >= 0)
×
437
                {
438
                    _current = new(remaining.Offset, index);
×
439
                    _remaining = new(
×
440
                        remaining.Offset + index + separator.Length,
×
441
                        remaining.Length - index - separator.Length);
×
442
                }
443
                else
444
                {
445
                    _current = remaining;
×
446
                    _remaining = default;
×
447
                }
448

449
                return true;
×
450
            }
451

452
            return false;
×
453
        }
454

455
        readonly object IEnumerator.Current => Current;
×
456
        readonly void IEnumerator.Reset() => throw new NotSupportedException();
×
457
        readonly void IDisposable.Dispose() { }
×
458
    }
459

460
    readonly void ICollection<U8String>.Add(U8String item) => throw new NotSupportedException();
×
461
    readonly void ICollection<U8String>.Clear() => throw new NotSupportedException();
×
462
    readonly bool ICollection<U8String>.Remove(U8String item) => throw new NotSupportedException();
×
463
}
464

465
// TODO: Optimize even more. This design is far from the northstar of perfect codegen
466
// but it still somehow manages to outperform Rust split iterators
467
public struct U8Split<TSeparator> :
468
    ICollection<U8String>,
469
    IU8Enumerable<U8Split<TSeparator>.Enumerator>
470
        where TSeparator : unmanaged
471
{
472
    readonly U8String _value;
473
    readonly TSeparator _separator;
474
    int _count;
475

476
    internal U8Split(U8String value, TSeparator separator)
477
    {
478
        _value = value;
×
479
        _separator = separator;
×
480
        _count = value.IsEmpty ? 0 : -1;
×
481
    }
×
482

483
    public int Count
484
    {
485
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
486
        get
487
        {
488
            var count = _count;
×
489
            if (count >= 0)
×
490
            {
491
                return count;
×
492
            }
493

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

498
            static int Count(ReadOnlySpan<byte> value, TSeparator separator)
499
            {
500
                return U8Searching.Count(value, separator);
×
501
            }
502
        }
503
    }
504

505
    public readonly bool Contains(U8String item)
506
    {
507
        return U8Searching.SplitContains(_value, _separator, item);
×
508
    }
509

510
    public void CopyTo(U8String[] array, int index)
511
    {
512
        this.CopyTo<U8Split<TSeparator>, Enumerator, U8String>(array.AsSpan()[index..]);
×
513
    }
×
514

515
    public readonly void Deconstruct(out U8String first, out U8String second)
516
    {
517
        this.Deconstruct<U8Split<TSeparator>, Enumerator, U8String>(out first, out second);
×
518
    }
×
519

520
    public readonly void Deconstruct(out U8String first, out U8String second, out U8String third)
521
    {
522
        this.Deconstruct<U8Split<TSeparator>, Enumerator, U8String>(out first, out second, out third);
×
523
    }
×
524

525
    public readonly U8String ElementAt(int index)
526
    {
527
        return this.ElementAt<U8Split<TSeparator>, Enumerator, U8String>(index);
×
528
    }
529

530
    public readonly U8String ElementAtOrDefault(int index)
531
    {
532
        return this.ElementAtOrDefault<U8Split<TSeparator>, Enumerator, U8String>(index);
×
533
    }
534

535
    public U8String[] ToArray() => this.ToArray<U8Split<TSeparator>, Enumerator, U8String>();
×
536
    public List<U8String> ToList() => this.ToList<U8Split<TSeparator>, Enumerator, U8String>();
×
537

538
    /// <summary>
539
    /// Returns a <see cref="Enumerator"/> over the provided string.
540
    /// </summary>
541
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
542
    public readonly Enumerator GetEnumerator() => new(_value, _separator);
×
543

544
    readonly IEnumerator<U8String> IEnumerable<U8String>.GetEnumerator() => GetEnumerator();
×
545
    readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
×
546
    readonly bool ICollection<U8String>.IsReadOnly => true;
×
547

548
    public struct Enumerator : IU8Enumerator
549
    {
550
        readonly byte[]? _value;
551
        readonly TSeparator _separator;
552
        U8Range _current;
553
        U8Range _remaining;
554

555
        internal Enumerator(U8String value, TSeparator separator)
556
        {
557
            _value = value._value;
×
558
            _separator = separator;
×
559
            _remaining = value._inner;
×
560
        }
×
561

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

564
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
565
        public bool MoveNext()
566
        {
567
            var remaining = _remaining;
×
568
            if (remaining.Length > 0)
×
569
            {
570
                var value = _value!.SliceUnsafe(remaining.Offset, remaining.Length);
×
571
                var index = U8Searching.IndexOf(value, _separator, out var size);
×
572
                if (index >= 0)
×
573
                {
574
                    _current = new(remaining.Offset, index);
×
575
                    _remaining = new(
×
576
                        remaining.Offset + index + size,
×
577
                        remaining.Length - index - size);
×
578
                }
579
                else
580
                {
581
                    _current = remaining;
×
582
                    _remaining = default;
×
583
                }
584

585
                return true;
×
586
            }
587

588
            return false;
×
589
        }
590

591
        readonly object IEnumerator.Current => Current;
×
592
        readonly void IEnumerator.Reset() => throw new NotSupportedException();
×
593
        readonly void IDisposable.Dispose() { }
×
594
    }
595

596
    readonly void ICollection<U8String>.Add(U8String item) => throw new NotSupportedException();
×
597
    readonly void ICollection<U8String>.Clear() => throw new NotSupportedException();
×
598
    readonly bool ICollection<U8String>.Remove(U8String item) => throw new NotSupportedException();
×
599
}
600

601
public readonly struct ConfiguredU8Split :
602
    IU8Enumerable<ConfiguredU8Split.Enumerator>
603
{
604
    readonly U8String _value;
605
    readonly U8String _separator;
606
    readonly U8SplitOptions _options;
607

608
    public ConfiguredU8Split(U8String value, U8String separator, U8SplitOptions options)
609
    {
610
        _value = value;
×
611
        _separator = separator;
×
612
        _options = options;
×
613
    }
×
614

615
    public readonly void Deconstruct(out U8String first, out U8String second)
616
    {
617
        this.Deconstruct<ConfiguredU8Split, Enumerator, U8String>(out first, out second);
×
618
    }
×
619

620
    public readonly void Deconstruct(out U8String first, out U8String second, out U8String third)
621
    {
622
        this.Deconstruct<ConfiguredU8Split, Enumerator, U8String>(out first, out second, out third);
×
623
    }
×
624

625
    public readonly U8String ElementAt(int index)
626
    {
627
        return this.ElementAt<ConfiguredU8Split, Enumerator, U8String>(index);
×
628
    }
629

630
    public readonly U8String ElementAtOrDefault(int index)
631
    {
632
        return this.ElementAtOrDefault<ConfiguredU8Split, Enumerator, U8String>(index);
×
633
    }
634

635
    public readonly Enumerator GetEnumerator() => new(_value, _separator, _options);
×
636

637
    readonly IEnumerator<U8String> IEnumerable<U8String>.GetEnumerator() => GetEnumerator();
×
638
    readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
×
639

640
    public struct Enumerator : IU8Enumerator
641
    {
642
        readonly byte[]? _value;
643
        readonly U8String _separator;
644
        readonly U8SplitOptions _options;
645
        U8Range _current;
646
        U8Range _remaining;
647

648
        public Enumerator(U8String value, U8String separator, U8SplitOptions options)
649
        {
650
            _value = value._value;
×
651
            _separator = separator;
×
652
            _options = options;
×
653
            _remaining = value._inner;
×
654
        }
×
655

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

658
        // TODO: Not most efficient but it works for now
659
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
660
        public bool MoveNext()
661
        {
662
        Next:
663
            var remaining = _remaining;
×
664
            if (remaining.Length > 0)
×
665
            {
666
                var value = _value!.SliceUnsafe(remaining.Offset, remaining.Length);
×
667
                var separator = _separator;
×
668
                var index = value.IndexOf(separator);
×
669
                if (index >= 0)
×
670
                {
671
                    _current = (_options & U8SplitOptions.Trim) != U8SplitOptions.Trim
×
672
                        ? new(remaining.Offset, index)
×
673
                        : TrimEntry(_value!, new(remaining.Offset, index));
×
674
                    _remaining = new(
×
675
                        remaining.Offset + index + separator.Length,
×
676
                        remaining.Length - index - separator.Length);
×
677
                }
678
                else
679
                {
680
                    _current = (_options & U8SplitOptions.Trim) != U8SplitOptions.Trim
×
681
                        ? remaining
×
682
                        : TrimEntry(_value!, remaining);
×
683
                    _remaining = default;
×
684
                }
685

686
                if ((_options & U8SplitOptions.RemoveEmpty) is U8SplitOptions.RemoveEmpty
×
687
                    && _current.Length is 0)
×
688
                {
689
                    goto Next;
690
                }
691

692
                return true;
×
693
            }
694

695
            return false;
×
696
        }
697

698
        private static U8Range TrimEntry(byte[] value, U8Range range)
699
        {
700
            // This could have been done better but works for now.
701
            return new U8String(value, range).Trim()._inner;
×
702
        }
703

704
        readonly object IEnumerator.Current => Current;
×
705
        readonly void IEnumerator.Reset() => throw new NotSupportedException();
×
706
        readonly void IDisposable.Dispose() { }
×
707
    }
708
}
709

710
public readonly struct ConfiguredU8Split<TSeparator> :
711
    IU8Enumerable<ConfiguredU8Split<TSeparator>.Enumerator>
712
        where TSeparator : unmanaged
713
{
714
    readonly U8String _value;
715
    readonly TSeparator _separator;
716
    readonly U8SplitOptions _options;
717

718
    internal ConfiguredU8Split(U8String value, TSeparator separator, U8SplitOptions options)
719
    {
720
        _value = value;
×
721
        _separator = separator;
×
722
        _options = options;
×
723
    }
×
724

725
    public readonly void Deconstruct(out U8String first, out U8String second)
726
    {
727
        this.Deconstruct<ConfiguredU8Split<TSeparator>, Enumerator, U8String>(out first, out second);
×
728
    }
×
729

730
    public readonly void Deconstruct(out U8String first, out U8String second, out U8String third)
731
    {
732
        this.Deconstruct<ConfiguredU8Split<TSeparator>, Enumerator, U8String>(out first, out second, out third);
×
733
    }
×
734

735
    public readonly U8String ElementAt(int index)
736
    {
737
        return this.ElementAt<ConfiguredU8Split<TSeparator>, Enumerator, U8String>(index);
×
738
    }
739

740
    public readonly U8String ElementAtOrDefault(int index)
741
    {
742
        return this.ElementAtOrDefault<ConfiguredU8Split<TSeparator>, Enumerator, U8String>(index);
×
743
    }
744

745
    public readonly Enumerator GetEnumerator() => new(_value, _separator, _options);
×
746

747
    readonly IEnumerator<U8String> IEnumerable<U8String>.GetEnumerator() => GetEnumerator();
×
748
    readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
×
749

750
    public struct Enumerator : IU8Enumerator
751
    {
752
        readonly byte[]? _value;
753
        readonly TSeparator _separator;
754
        readonly U8SplitOptions _options;
755
        U8Range _current;
756
        U8Range _remaining;
757

758
        internal Enumerator(U8String value, TSeparator separator, U8SplitOptions options)
759
        {
760
            _value = value._value;
×
761
            _separator = separator;
×
762
            _options = options;
×
763
            _remaining = value._inner;
×
764
        }
×
765

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

768
        // TODO: Not most efficient but it works for now
769
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
770
        public bool MoveNext()
771
        {
772
        Next:
773
            var remaining = _remaining;
×
774
            if (remaining.Length > 0)
×
775
            {
776
                var value = _value!.SliceUnsafe(remaining.Offset, remaining.Length);
×
777
                var index = U8Searching.IndexOf(value, _separator, out var size);
×
778
                if (index >= 0)
×
779
                {
780
                    _current = (_options & U8SplitOptions.Trim) != U8SplitOptions.Trim
×
781
                        ? new(remaining.Offset, index)
×
782
                        : TrimEntry(_value!, new(remaining.Offset, index));
×
783
                    _remaining = new(
×
784
                        remaining.Offset + index + size,
×
785
                        remaining.Length - index - size);
×
786
                }
787
                else
788
                {
789
                    _current = (_options & U8SplitOptions.Trim) != U8SplitOptions.Trim
×
790
                        ? remaining
×
791
                        : TrimEntry(_value!, remaining);
×
792
                    _remaining = default;
×
793
                }
794

795
                if ((_options & U8SplitOptions.RemoveEmpty) is U8SplitOptions.RemoveEmpty
×
796
                    && _current.Length is 0)
×
797
                {
798
                    goto Next;
799
                }
800

801
                return true;
×
802
            }
803

804
            return false;
×
805
        }
806

807
        private static U8Range TrimEntry(byte[] value, U8Range range)
808
        {
809
            // This could have been done better but works for now.
810
            return new U8String(value, range).Trim()._inner;
×
811
        }
812

813
        readonly object IEnumerator.Current => Current;
×
814
        readonly void IEnumerator.Reset() => throw new NotSupportedException();
×
815
        readonly void IDisposable.Dispose() { }
×
816
    }
817
}
818

819
// Unfortunately, because ref structs cannot be used as generic type arguments,
820
// we have to resort to duplicating the implementation and exposing methods manually.
821
// What's worse, ConfiguredU8RefSplit has to be duplicated as well. Things we do for performance...
822
// TODO: Implement this
823
// TODO 2: Eventually, remove duplicate code once ref structs can be used as generics.
824
public readonly ref struct U8RefSplit
825
{
826
    readonly U8String _value;
827
    readonly ReadOnlySpan<byte> _separator;
828

829
    internal U8RefSplit(U8String value, ReadOnlySpan<byte> separator)
830
    {
831
        _value = value;
×
832
        _separator = separator;
×
833
    }
×
834

835
    public int Count()
836
    {
837
        var split = this;
×
838
        if (split._value.Length > 0)
×
839
        {
840
            return U8Searching.Count(
×
841
                split._value.UnsafeSpan,
×
842
                split._separator) + 1;
×
843
        }
844

845
        return 0;
×
846
    }
847

848
    public bool Contains(U8String item)
849
    {
850
        return U8Searching.SplitContains(_value, _separator, item);
×
851
    }
852

853
    public void CopyTo(U8String[] array, int index)
854
    {
855
        var span = array.AsSpan()[index..];
×
856
        var split = this;
×
857
        if (split._value.Length > 0)
×
858
        {
859
            var count = split.Count();
×
860
            span = span[..count];
×
861

862
            var i = 0;
×
863
            ref var dst = ref span.AsRef();
×
864
            foreach (var item in split)
×
865
            {
866
                dst.Add(i++) = item;
×
867
            }
868
        }
869
    }
×
870

871
    public void Deconstruct(out U8String first, out U8String second)
872
    {
873
        (first, second) = (default, default);
×
874

875
        var enumerator = GetEnumerator();
×
876
        if (enumerator.MoveNext())
×
877
        {
878
            first = enumerator.Current;
×
879
            if (enumerator.MoveNext())
×
880
            {
881
                second = enumerator.Current;
×
882
            }
883
        }
884
    }
×
885

886
    public void Deconstruct(out U8String first, out U8String second, out U8String third)
887
    {
888
        (first, second, third) = (default, default, default);
×
889

890
        var enumerator = GetEnumerator();
×
891
        if (enumerator.MoveNext())
×
892
        {
893
            first = enumerator.Current;
×
894
            if (enumerator.MoveNext())
×
895
            {
896
                second = enumerator.Current;
×
897
                if (enumerator.MoveNext())
×
898
                {
899
                    third = enumerator.Current;
×
900
                }
901
            }
902
        }
903
    }
×
904

905
    public U8String ElementAt(int index)
906
    {
907
        if (index < 0)
×
908
        {
909
            ThrowHelpers.ArgumentOutOfRange();
×
910
        }
911

912
        var enumerator = GetEnumerator();
×
913
        while (enumerator.MoveNext())
×
914
        {
915
            if (index is 0)
×
916
            {
917
                return enumerator.Current;
×
918
            }
919

920
            index--;
×
921
        }
922

923
        return ThrowHelpers.ArgumentOutOfRange<U8String>();
×
924
    }
925

926
    public U8String ElementAtOrDefault(int index)
927
    {
928
        if (index < 0)
×
929
        {
930
            return default;
×
931
        }
932

933
        var enumerator = GetEnumerator();
×
934
        while (enumerator.MoveNext())
×
935
        {
936
            if (index is 0)
×
937
            {
938
                return enumerator.Current;
×
939
            }
940

941
            index--;
×
942
        }
943

944
        return default;
×
945
    }
946

947
    public U8String[] ToArray()
948
    {
949
        var split = this;
×
950
        if (split._value.Length > 0)
×
951
        {
952
            var count = split.Count();
×
953
            var result = new U8String[count];
×
954
            var span = result.AsSpan();
×
955

956
            var i = 0;
×
957
            ref var dst = ref span.AsRef();
×
958
            foreach (var item in split)
×
959
            {
960
                dst.Add(i++) = item;
×
961
            }
962

963
            return result;
×
964
        }
965

966
        return Array.Empty<U8String>();
×
967
    }
968

969
    public List<U8String> ToList()
970
    {
971
        var split = this;
×
972
        if (split._value.Length > 0)
×
973
        {
974
            var count = split.Count();
×
975
            var result = new List<U8String>(count);
×
976
            CollectionsMarshal.SetCount(result, count);
×
977
            var span = CollectionsMarshal.AsSpan(result);
×
978

979
            var i = 0;
×
980
            ref var dst = ref span.AsRef();
×
981
            foreach (var item in split)
×
982
            {
983
                dst.Add(i++) = item;
×
984
            }
985

986
            return result;
×
987
        }
988

989
        return new List<U8String>();
×
990
    }
991

992
    public readonly Enumerator GetEnumerator() => new(_value, _separator);
×
993

994
    public ref struct Enumerator
995
    {
996
        readonly byte[]? _value;
997
        readonly ReadOnlySpan<byte> _separator;
998
        U8Range _current;
999
        U8Range _remaining;
1000

1001
        internal Enumerator(U8String value, ReadOnlySpan<byte> separator)
1002
        {
1003
            _value = value._value;
×
1004
            _separator = separator;
×
1005
            _remaining = value._inner;
×
1006
        }
×
1007

1008
        public readonly U8String Current
1009
        {
1010
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
1011
            get => new(_value, _current.Offset, _current.Length);
×
1012
        }
1013

1014
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
1015
        public bool MoveNext()
1016
        {
1017
            var remaining = _remaining;
×
1018
            if (remaining.Length > 0)
×
1019
            {
1020
                var value = _value!.SliceUnsafe(remaining.Offset, remaining.Length);
×
1021
                var separator = _separator;
×
1022
                var index = U8Searching.IndexOf(value, separator);
×
1023
                if (index >= 0)
×
1024
                {
1025
                    _current = new(remaining.Offset, index);
×
1026
                    _remaining = new(
×
1027
                        remaining.Offset + index + separator.Length,
×
1028
                        remaining.Length - index - separator.Length);
×
1029
                }
1030
                else
1031
                {
1032
                    _current = remaining;
×
1033
                    _remaining = default;
×
1034
                }
1035

1036
                return true;
×
1037
            }
1038

1039
            return false;
×
1040
        }
1041
    }
1042
}
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