< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 62
Coverable lines: 62
Total lines: 235
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 55
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%110%
GetLengthOfFirstExtendedGraphemeCluster(...)0%55550%
GetLengthOfFirstUtf16ExtendedGraphemeCluster(...)100%110%
.ctor(...)100%110%
MoveNext()100%110%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Text/Unicode/TextSegmentationUtility.cs

#LineLine coverage
 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
 4using System.Buffers;
 5using System.Globalization;
 6using System.Runtime.InteropServices;
 7
 8namespace System.Text.Unicode
 9{
 10    /// <summary>
 11    /// Provides helper utilities for computing text segmentation boundaries
 12    /// as specified in UAX#29 (https://www.unicode.org/reports/tr29/).
 13    /// </summary>
 14    /// <remarks>
 15    /// The current implementation is compliant per Rev. 35, https://www.unicode.org/reports/tr29/tr29-35.html.
 16    /// </remarks>
 17    internal static class TextSegmentationUtility
 18    {
 19        private delegate OperationStatus DecodeFirstRune<T>(ReadOnlySpan<T> input, out Rune rune, out int elementsConsum
 20
 021        private static readonly DecodeFirstRune<char> _utf16Decoder = Rune.DecodeFromUtf16;
 22
 23        private static int GetLengthOfFirstExtendedGraphemeCluster<T>(ReadOnlySpan<T> input, DecodeFirstRune<T> decoder)
 24        {
 25            // Algorithm given at https://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules.
 26
 027            Processor<T> processor = new Processor<T>(input, decoder);
 028            processor.MoveNext();
 29
 30            // First, consume as many Prepend scalars as we can (rule GB9b).
 31
 032            while (processor.CurrentType == GraphemeClusterBreakType.Prepend)
 33            {
 034                processor.MoveNext();
 35            }
 36
 37            // Next, make sure we're not about to violate control character restrictions.
 38            // Essentially, if we saw Prepend data, we can't have Control | CR | LF data afterward (rule GB5).
 39
 040            if (processor.CurrentCodeUnitOffset > 0)
 41            {
 042                if (processor.CurrentType == GraphemeClusterBreakType.Control
 043                    || processor.CurrentType == GraphemeClusterBreakType.CR
 044                    || processor.CurrentType == GraphemeClusterBreakType.LF)
 45                {
 46                    goto Return;
 47                }
 48            }
 49
 50            // Now begin the main state machine.
 51
 052            GraphemeClusterBreakType previousClusterBreakType = processor.CurrentType;
 053            processor.MoveNext();
 54
 55            switch (previousClusterBreakType)
 56            {
 57                case GraphemeClusterBreakType.CR:
 058                    if (processor.CurrentType != GraphemeClusterBreakType.LF)
 59                    {
 60                        goto Return; // rules GB3 & GB4 (only <LF> can follow <CR>)
 61                    }
 62
 063                    processor.MoveNext();
 064                    goto case GraphemeClusterBreakType.LF;
 65
 66                case GraphemeClusterBreakType.Control:
 67                case GraphemeClusterBreakType.LF:
 68                    goto Return; // rule GB4 (no data after Control | LF)
 69
 70                case GraphemeClusterBreakType.L:
 071                    if (processor.CurrentType == GraphemeClusterBreakType.L)
 72                    {
 073                        processor.MoveNext(); // rule GB6 (L x L)
 074                        goto case GraphemeClusterBreakType.L;
 75                    }
 076                    else if (processor.CurrentType == GraphemeClusterBreakType.V)
 77                    {
 078                        processor.MoveNext(); // rule GB6 (L x V)
 079                        goto case GraphemeClusterBreakType.V;
 80                    }
 081                    else if (processor.CurrentType == GraphemeClusterBreakType.LV)
 82                    {
 083                        processor.MoveNext(); // rule GB6 (L x LV)
 084                        goto case GraphemeClusterBreakType.LV;
 85                    }
 086                    else if (processor.CurrentType == GraphemeClusterBreakType.LVT)
 87                    {
 088                        processor.MoveNext(); // rule GB6 (L x LVT)
 089                        goto case GraphemeClusterBreakType.LVT;
 90                    }
 91                    else
 92                    {
 93                        break;
 94                    }
 95
 96                case GraphemeClusterBreakType.LV:
 97                case GraphemeClusterBreakType.V:
 098                    if (processor.CurrentType == GraphemeClusterBreakType.V)
 99                    {
 0100                        processor.MoveNext(); // rule GB7 (LV | V x V)
 0101                        goto case GraphemeClusterBreakType.V;
 102                    }
 0103                    else if (processor.CurrentType == GraphemeClusterBreakType.T)
 104                    {
 0105                        processor.MoveNext(); // rule GB7 (LV | V x T)
 106                        goto case GraphemeClusterBreakType.T;
 107                    }
 108                    else
 109                    {
 110                        break;
 111                    }
 112
 113                case GraphemeClusterBreakType.LVT:
 114                case GraphemeClusterBreakType.T:
 0115                    if (processor.CurrentType == GraphemeClusterBreakType.T)
 116                    {
 0117                        processor.MoveNext(); // rule GB8 (LVT | T x T)
 0118                        goto case GraphemeClusterBreakType.T;
 119                    }
 120                    else
 121                    {
 122                        break;
 123                    }
 124
 125                case GraphemeClusterBreakType.Extended_Pictograph:
 126                    // Attempt processing extended pictographic (rules GB11, GB9).
 127                    // First, drain any Extend scalars that might exist
 128
 0129                    while (processor.CurrentType == GraphemeClusterBreakType.Extend)
 130                    {
 0131                        processor.MoveNext();
 132                    }
 133
 134                    // Now see if there's a ZWJ + extended pictograph again.
 135
 0136                    if (processor.CurrentType != GraphemeClusterBreakType.ZWJ)
 137                    {
 138                        break;
 139                    }
 140
 0141                    processor.MoveNext();
 0142                    if (processor.CurrentType != GraphemeClusterBreakType.Extended_Pictograph)
 143                    {
 144                        break;
 145                    }
 146
 0147                    processor.MoveNext();
 0148                    goto case GraphemeClusterBreakType.Extended_Pictograph;
 149
 150                case GraphemeClusterBreakType.Regional_Indicator:
 151                    // We've consumed a single RI scalar. Try to consume another (to make it a pair).
 152
 0153                    if (processor.CurrentType == GraphemeClusterBreakType.Regional_Indicator)
 154                    {
 0155                        processor.MoveNext();
 156                    }
 157
 158                    // Standalone RI scalars (or a single pair of RI scalars) can only be followed by trailers.
 159
 0160                    break; // nothing but trailers after the final RI
 161
 162                default:
 163                    break;
 164            }
 165
 166            // rules GB9, GB9a
 0167            while (processor.CurrentType == GraphemeClusterBreakType.Extend
 0168                || processor.CurrentType == GraphemeClusterBreakType.ZWJ
 0169                || processor.CurrentType == GraphemeClusterBreakType.SpacingMark)
 170            {
 0171                processor.MoveNext();
 172            }
 173
 174        Return:
 175
 0176            return processor.CurrentCodeUnitOffset; // rules GB2, GB999
 177        }
 178
 179        /// <summary>
 180        /// Given UTF-16 input text, returns the length (in chars) of the first extended grapheme cluster.
 181        /// The slice [0..length] represents the first standalone extended grapheme cluster in the text.
 182        /// If the input is empty, returns 0.
 183        /// </summary>
 184        public static int GetLengthOfFirstUtf16ExtendedGraphemeCluster(ReadOnlySpan<char> input)
 185        {
 0186            return GetLengthOfFirstExtendedGraphemeCluster(input, _utf16Decoder);
 187        }
 188
 189        [StructLayout(LayoutKind.Auto)]
 190        private ref struct Processor<T>
 191        {
 192            private readonly ReadOnlySpan<T> _buffer;
 193            private readonly DecodeFirstRune<T> _decoder;
 194            private int _codeUnitLengthOfCurrentScalar;
 195
 196            internal Processor(ReadOnlySpan<T> buffer, DecodeFirstRune<T> decoder)
 197            {
 0198                _buffer = buffer;
 0199                _decoder = decoder;
 0200                _codeUnitLengthOfCurrentScalar = 0;
 0201                CurrentType = GraphemeClusterBreakType.Other;
 0202                CurrentCodeUnitOffset = 0;
 0203            }
 204
 0205            public int CurrentCodeUnitOffset { get; private set; }
 206
 207            /// <summary>
 208            /// Will be <see cref="GraphemeClusterBreakType.Other"/> if invalid data or EOF reached.
 209            /// Caller shouldn't need to special-case this since the normal rules will halt on this condition.
 210            /// </summary>
 0211            public GraphemeClusterBreakType CurrentType { get; private set; }
 212
 213            public void MoveNext()
 214            {
 215                // For ill-formed subsequences (like unpaired UTF-16 surrogate code points), we rely on
 216                // the decoder's default behavior of interpreting these ill-formed subsequences as
 217                // equivalent to U+FFFD REPLACEMENT CHARACTER. This code point has a boundary property
 218                // of Other (XX), which matches the modifications made to UAX#29, Rev. 35.
 219                // See: https://www.unicode.org/reports/tr29/tr29-35.html#Modifications
 220                // This change is also reflected in the UCD files. For example, Unicode 11.0's UCD file
 221                // https://www.unicode.org/Public/11.0.0/ucd/auxiliary/GraphemeBreakProperty.txt
 222                // has the line "D800..DFFF    ; Control # Cs [2048] <surrogate-D800>..<surrogate-DFFF>",
 223                // but starting with Unicode 12.0 that line has been removed.
 224                //
 225                // If a later version of the Unicode Standard further modifies this guidance we should reflect
 226                // that here.
 227
 0228                CurrentCodeUnitOffset += _codeUnitLengthOfCurrentScalar;
 0229                _decoder(_buffer.Slice(CurrentCodeUnitOffset), out Rune thisRune, out _codeUnitLengthOfCurrentScalar);
 0230                CurrentType = CharUnicodeInfo.GetGraphemeClusterBreakType(thisRune);
 0231            }
 232        }
 233    }
 234}
 235