< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 263
Coverable lines: 263
Total lines: 560
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 136
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%
ToUpper(...)0%660%
ToUpperOrdinal(...)0%10100%
ToLower(...)0%660%
ToLowerOrdinal(...)0%10100%
CompareStringIgnoreCase(...)0%30300%
IndexOf(...)0%24240%
LastIndexOf(...)0%24240%
InitCasingTable()0%440%
InitOrdinalCasingPage(...)0%660%
InitLowerCasingTable()0%440%
InitOrdinalLowerCasingPage(...)0%12120%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/Globalization/OrdinalCasing.Icu.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.Diagnostics;
 5using System.Runtime.CompilerServices;
 6using System.Runtime.InteropServices;
 7using System.Text;
 8using System.Threading;
 9
 10namespace System.Globalization
 11{
 12    internal static partial class OrdinalCasing
 13    {
 14        // NoCasingPage means the Unicode page doesn't support any casing and no case translation is needed.
 015        private static ushort[] NoCasingPage => [];
 16
 17        // s_basicLatin is covering the casing for the Basic Latin & C0 Controls range.
 18        // we are not lazy initializing this range because it is the most common used range and we'll cache it anyway ve
 019        private static readonly ushort[] s_basicLatin =
 020        [
 021            // Upper Casing
 022
 023            /* 0000-000f */  0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x0
 024            /* 0010-001f */  0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x0
 025            /* 0020-002f */  0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x0
 026            /* 0030-003f */  0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x0
 027            /* 0040-004f */  0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x0
 028            /* 0050-005f */  0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x0
 029            /* 0060-006f */  0x0060, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x0
 030            /* 0070-007f */  0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x0
 031            /* 0080-008f */  0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008a, 0x0
 032            /* 0090-009f */  0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009a, 0x0
 033            /* 00a0-00af */  0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x0
 034            /* 00b0-00bf */  0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x039c, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x0
 035            /* 00c0-00cf */  0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x0
 036            /* 00d0-00df */  0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da, 0x0
 037            /* 00e0-00ef */  0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x0
 038            /* 00f0-00ff */  0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00f7, 0x00d8, 0x00d9, 0x00da, 0x0
 039        ];
 40
 41        // s_casingTable is covering the Unicode BMP plane only. Surrogate casing is handled separately.
 42        // Every cell in the table is covering the casing of 256 characters in the BMP.
 43        // Every cell is array of 512 character for uppercasing mapping.
 044        private static readonly ushort[]?[] s_casingTable = InitCasingTable();
 45
 46        // s_lowerBasicLatin covers the lower casing for the Basic Latin & C0 Controls range.
 47        // We don't lazily initialize this range because it is the most commonly used range and we'll cache it anyway ve
 048        private static readonly ushort[] s_lowerBasicLatin =
 049        [
 050            // Lower Casing
 051
 052            /* 0000-000f */  0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x0
 053            /* 0010-001f */  0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x0
 054            /* 0020-002f */  0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x0
 055            /* 0030-003f */  0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x0
 056            /* 0040-004f */  0x0040, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x0
 057            /* 0050-005f */  0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x0
 058            /* 0060-006f */  0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x0
 059            /* 0070-007f */  0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x0
 060            /* 0080-008f */  0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008a, 0x0
 061            /* 0090-009f */  0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009a, 0x0
 062            /* 00a0-00af */  0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x0
 063            /* 00b0-00bf */  0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x0
 064            /* 00c0-00cf */  0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x0
 065            /* 00d0-00df */  0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00d7, 0x00f8, 0x00f9, 0x00fa, 0x0
 066            /* 00e0-00ef */  0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x0
 067            /* 00f0-00ff */  0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x0
 068        ];
 69
 70        // s_lowerCasingTable mirrors s_casingTable but holds the simple lower casing mapping for the BMP.
 71        // It is pre-seeded with the shared NoCasingPage for the permanently uncased pages (see InitLowerCasingTable);
 72        // remaining pages are initialized lazily through ICU and identity pages collapse to the shared NoCasingPage.
 073        private static readonly ushort[]?[] s_lowerCasingTable = InitLowerCasingTable();
 74
 75        /*
 76         The table is initialized to:
 77        {
 78            // 0000-07FF //       s_basicLatin,            null,            null,            null,            null,     
 79            // 0800-0FFF //               null,            null,            null,            null,            null,     
 80            // 1000-17FF //               null,    NoCasingPage,            null,            null,    NoCasingPage,    N
 81            // 1800-1FFF //               null,            null,            null,            null,            null,     
 82            // 2000-27FF //               null,            null,    NoCasingPage,    NoCasingPage,            null,    N
 83            // 2800-2FFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,            null,            null,     
 84            // 3000-37FF //               null,            null,            null,    NoCasingPage,    NoCasingPage,    N
 85            // 3800-3FFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 86            // 4000-47FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 87            // 4800-4FFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 88            // 5000-57FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 89            // 5800-5FFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 90            // 6000-67FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 91            // 6800-6FFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 92            // 7000-77FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 93            // 7800-7FFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 94            // 8000-87FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 95            // 8800-8FFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 96            // 9000-97FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 97            // 9800-9FFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 98            // A000-A7FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,            null,    N
 99            // A800-AFFF //               null,            null,            null,            null,    NoCasingPage,    N
 100            // B000-B7FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 101            // B800-BFFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 102            // C000-C7FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 103            // C800-CFFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 104            // D000-D7FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 105            // D800-DFFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 106            // E000-E7FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 107            // E800-EFFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 108            // F000-F7FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    N
 109            // F800-FFFF //       NoCasingPage,    NoCasingPage,            null,            null,    NoCasingPage,     
 110        };
 111*/
 112
 113        // 0 - null
 114        // 1 - NoCasingPage
 115        // The bits are in reverse order for readability, i.e. the highest order bit refers to
 116        // the lowest index.
 117        private static ReadOnlySpan<byte> s_casingTableInit => // 32
 0118        [
 0119            /* 0000-07FF */    0b00000000,
 0120            /* 0800-0FFF */    0b00000000,
 0121            /* 1000-17FF */    0b01001100,
 0122            /* 1800-1FFF */    0b00000000,
 0123            /* 2000-27FF */    0b00110111,
 0124            /* 2800-2FFF */    0b11100000,
 0125            /* 3000-37FF */    0b00011111,
 0126            /* 3800-3FFF */    0b11111111,
 0127            /* 4000-47FF */    0b11111111,
 0128            /* 4800-4FFF */    0b11111111,
 0129            /* 5000-57FF */    0b11111111,
 0130            /* 5800-5FFF */    0b11111111,
 0131            /* 6000-67FF */    0b11111111,
 0132            /* 6800-6FFF */    0b11111111,
 0133            /* 7000-77FF */    0b11111111,
 0134            /* 7800-7FFF */    0b11111111,
 0135            /* 8000-87FF */    0b11111111,
 0136            /* 8800-8FFF */    0b11111111,
 0137            /* 9000-97FF */    0b11111111,
 0138            /* 9800-9FFF */    0b11111110,
 0139            /* A000-A7FF */    0b11110100,
 0140            /* A800-AFFF */    0b00001111,
 0141            /* B000-B7FF */    0b11111111,
 0142            /* B800-BFFF */    0b11111111,
 0143            /* C000-C7FF */    0b11111111,
 0144            /* C800-CFFF */    0b11111111,
 0145            /* D000-D7FF */    0b11111110,
 0146            /* D800-DFFF */    0b11111111,
 0147            /* E000-E7FF */    0b11111111,
 0148            /* E800-EFFF */    0b11111111,
 0149            /* F000-F7FF */    0b11111111,
 0150            /* F800-FFFF */    0b11001000,
 0151        ];
 152
 153        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 154        internal static char ToUpper(char c)
 155        {
 0156            int pageNumber = ((int)c) >> 8;
 0157            if (pageNumber == 0) // optimize for ASCII range
 158            {
 0159                return (char)s_basicLatin[(int)c];
 160            }
 161
 0162            ushort[]? casingTable = s_casingTable[pageNumber];
 163
 0164            if (casingTable == NoCasingPage)
 165            {
 0166                return c;
 167            }
 168
 0169            casingTable ??= InitOrdinalCasingPage(pageNumber);
 170
 0171            return (char)casingTable[((int)c) & 0xFF];
 172        }
 173
 174        internal static void ToUpperOrdinal(ReadOnlySpan<char> source, Span<char> destination)
 175        {
 0176            Debug.Assert(!GlobalizationMode.Invariant);
 0177            Debug.Assert(!GlobalizationMode.UseNls);
 178
 179            // Quickly upper-case the leading run of ASCII characters, then handle the remainder.
 0180            Ascii.ToUpper(source, destination, out int start);
 181
 0182            for (int i = start; (uint)i < (uint)source.Length; i++)
 183            {
 0184                char c = source[i];
 0185                if (c <= '\u00FF') // optimize ASCII/Latin
 186                {
 0187                    destination[i] = (char)s_basicLatin[c];
 0188                    continue;
 189                }
 190
 0191                if (char.IsHighSurrogate(c) && i < source.Length - 1)
 192                {
 0193                    char cl = source[i + 1];
 0194                    if (char.IsLowSurrogate(cl))
 195                    {
 196                        // well formed surrogates
 0197                        SurrogateCasing.ToUpper(c, cl, out destination[i], out destination[i + 1]);
 0198                        i++; // skip the low surrogate
 0199                        continue;
 200                    }
 201                }
 202
 0203                destination[i] = ToUpper(c);
 204            }
 0205        }
 206
 207        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 208        internal static char ToLower(char c)
 209        {
 0210            int pageNumber = ((int)c) >> 8;
 0211            if (pageNumber == 0) // optimize for ASCII range
 212            {
 0213                return (char)s_lowerBasicLatin[(int)c];
 214            }
 215
 0216            ushort[]? casingTable = s_lowerCasingTable[pageNumber];
 217
 0218            if (casingTable == NoCasingPage)
 219            {
 0220                return c;
 221            }
 222
 0223            casingTable ??= InitOrdinalLowerCasingPage(pageNumber);
 224
 0225            return (char)casingTable[((int)c) & 0xFF];
 226        }
 227
 228        internal static void ToLowerOrdinal(ReadOnlySpan<char> source, Span<char> destination)
 229        {
 0230            Debug.Assert(!GlobalizationMode.Invariant);
 0231            Debug.Assert(!GlobalizationMode.UseNls);
 232
 233            // Quickly lower-case the leading run of ASCII characters, then handle the remainder.
 0234            Ascii.ToLower(source, destination, out int start);
 235
 0236            for (int i = start; (uint)i < (uint)source.Length; i++)
 237            {
 0238                char c = source[i];
 0239                if (c <= '\u00FF') // optimize ASCII/Latin
 240                {
 0241                    destination[i] = (char)s_lowerBasicLatin[c];
 0242                    continue;
 243                }
 244
 0245                if (char.IsHighSurrogate(c) && i < source.Length - 1)
 246                {
 0247                    char cl = source[i + 1];
 0248                    if (char.IsLowSurrogate(cl))
 249                    {
 250                        // well formed surrogates
 0251                        SurrogateCasing.ToLower(c, cl, out destination[i], out destination[i + 1]);
 0252                        i++; // skip the low surrogate
 0253                        continue;
 254                    }
 255                }
 256
 0257                destination[i] = ToLower(c);
 258            }
 0259        }
 260
 261        internal static int CompareStringIgnoreCase(ref char strA, int lengthA, ref char strB, int lengthB)
 262        {
 0263            Debug.Assert(!GlobalizationMode.Invariant);
 0264            Debug.Assert(!GlobalizationMode.UseNls);
 265
 0266            int length = Math.Min(lengthA, lengthB);
 267
 0268            ref char charA = ref strA;
 0269            ref char charB = ref strB;
 270
 0271            int index = 0;
 272
 0273            while (index < length)
 274            {
 0275                char a = charA;
 0276                char b = charB;
 0277                char lowSurrogateA = '\0';
 278
 0279                if (!char.IsHighSurrogate(a) || index >= lengthA - 1 || !char.IsLowSurrogate(lowSurrogateA = Unsafe.Add(
 280                {
 0281                    if (!char.IsHighSurrogate(b) || index >= lengthB - 1 || !char.IsLowSurrogate(Unsafe.Add(ref charB, 1
 282                    {
 283                        //
 284                        // Neither A or B are surrogates
 285                        //
 286
 0287                        if (b == a)
 288                        {
 0289                            index++;
 0290                            charA = ref Unsafe.Add(ref charA, 1);
 0291                            charB = ref Unsafe.Add(ref charB, 1);
 0292                            continue;
 293                        }
 294
 0295                        char aUpper = ToUpper(a);
 0296                        char bUpper = ToUpper(b);
 297
 0298                        if (aUpper == bUpper)
 299                        {
 0300                            index++;
 0301                            charA = ref Unsafe.Add(ref charA, 1);
 0302                            charB = ref Unsafe.Add(ref charB, 1);
 0303                            continue;
 304                        }
 305
 0306                        return aUpper - bUpper;
 307                    }
 308
 309                    //
 310                    // charA is not surrogate and charB is valid surrogate
 311                    //
 312
 0313                    return -1;
 314                }
 315
 316                //
 317                // A is Surrogate
 318                //
 319
 0320                char lowSurrogateB = '\0';
 321
 0322                if (!char.IsHighSurrogate(b) || index >= lengthB - 1 || !char.IsLowSurrogate(lowSurrogateB = Unsafe.Add(
 323                {
 324                    //
 325                    // charB is not surrogate and charA is surrogate
 326                    //
 327
 0328                    return 1;
 329                }
 330
 331                //
 332                // charA and charB are surrogates
 333                //
 334
 0335                Debug.Assert(lowSurrogateA != '\0');
 0336                Debug.Assert(lowSurrogateB != '\0');
 337
 0338                if (a == b && lowSurrogateA == lowSurrogateB)
 339                {
 0340                    index += 2;
 0341                    charA = ref Unsafe.Add(ref charA, 2);
 0342                    charB = ref Unsafe.Add(ref charB, 2);
 0343                    continue;
 344                }
 345
 0346                uint upperSurrogateA = CharUnicodeInfo.ToUpper(UnicodeUtility.GetScalarFromUtf16SurrogatePair(a, lowSurr
 0347                uint upperSurrogateB = CharUnicodeInfo.ToUpper(UnicodeUtility.GetScalarFromUtf16SurrogatePair(b, lowSurr
 348
 0349                if (upperSurrogateA == upperSurrogateB)
 350                {
 0351                    index += 2;
 0352                    charA = ref Unsafe.Add(ref charA, 2);
 0353                    charB = ref Unsafe.Add(ref charB, 2);
 0354                    continue;
 355                }
 356
 0357                return (int)upperSurrogateA - (int)upperSurrogateB;
 358            }
 359
 0360            return lengthA - lengthB;
 361        }
 362
 363        internal static unsafe int IndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value)
 364        {
 0365            Debug.Assert(value.Length > 0);
 0366            Debug.Assert(value.Length <= source.Length);
 367
 0368            Debug.Assert(!GlobalizationMode.Invariant);
 0369            Debug.Assert(!GlobalizationMode.UseNls);
 370
 0371            fixed (char* pSource = &MemoryMarshal.GetReference(source))
 0372            fixed (char* pValue  = &MemoryMarshal.GetReference(value))
 373            {
 0374                char* pSourceLimit = pSource + (source.Length - value.Length);
 0375                char* pValueLimit = pValue + value.Length - 1;
 0376                char* pCurrentSource = pSource;
 377
 0378                while (pCurrentSource <= pSourceLimit)
 379                {
 0380                    char *pVal = pValue;
 0381                    char *pSrc = pCurrentSource;
 382
 0383                    while (pVal <= pValueLimit)
 384                    {
 0385                        if (!char.IsHighSurrogate(*pVal) || pVal == pValueLimit)
 386                        {
 0387                            if (*pVal != *pSrc && ToUpper(*pVal) != ToUpper(*pSrc))
 388                                break; // no match
 389
 0390                            pVal++;
 0391                            pSrc++;
 0392                            continue;
 393                        }
 394
 0395                        if (char.IsHighSurrogate(*pSrc) && char.IsLowSurrogate(*(pSrc + 1)) && char.IsLowSurrogate(*(pVa
 396                        {
 397                            // Well formed surrogates
 398                            // both the source and the Value have well-formed surrogates.
 0399                            if (!SurrogateCasing.Equal(*pSrc, *(pSrc + 1), *pVal, *(pVal + 1)))
 400                                break; // no match
 401
 0402                            pSrc += 2;
 0403                            pVal += 2;
 0404                            continue;
 405                        }
 406
 0407                        if (*pVal != *pSrc)
 408                            break; // no match
 409
 0410                        pSrc++;
 0411                        pVal++;
 412                    }
 413
 0414                    if (pVal > pValueLimit)
 415                    {
 416                        // Found match.
 0417                        return (int) (pCurrentSource - pSource);
 418                    }
 419
 0420                    pCurrentSource++;
 421                }
 422
 0423                return -1;
 424            }
 425        }
 426
 427        internal static unsafe int LastIndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value)
 428        {
 0429            Debug.Assert(value.Length > 0);
 0430            Debug.Assert(value.Length <= source.Length);
 431
 0432            Debug.Assert(!GlobalizationMode.Invariant);
 0433            Debug.Assert(!GlobalizationMode.UseNls);
 434
 0435            fixed (char* pSource = &MemoryMarshal.GetReference(source))
 0436            fixed (char* pValue  = &MemoryMarshal.GetReference(value))
 437            {
 0438                char* pValueLimit = pValue + value.Length - 1;
 0439                char* pCurrentSource = pSource + (source.Length - value.Length);
 440
 0441                while (pCurrentSource >= pSource)
 442                {
 0443                    char *pVal = pValue;
 0444                    char *pSrc = pCurrentSource;
 445
 0446                    while (pVal <= pValueLimit)
 447                    {
 0448                        if (!char.IsHighSurrogate(*pVal) || pVal == pValueLimit)
 449                        {
 0450                            if (*pVal != *pSrc && ToUpper(*pVal) != ToUpper(*pSrc))
 451                                break; // no match
 452
 0453                            pVal++;
 0454                            pSrc++;
 0455                            continue;
 456                        }
 457
 0458                        if (char.IsHighSurrogate(*pSrc) && char.IsLowSurrogate(*(pSrc + 1)) && char.IsLowSurrogate(*(pVa
 459                        {
 460                            // Well formed surrogates
 461                            // both the source and the Value have well-formed surrogates.
 0462                            if (!SurrogateCasing.Equal(*pSrc, *(pSrc + 1), *pVal, *(pVal + 1)))
 463                                break; // no match
 464
 0465                            pSrc += 2;
 0466                            pVal += 2;
 0467                            continue;
 468                        }
 469
 0470                        if (*pVal != *pSrc)
 471                            break; // no match
 472
 0473                        pSrc++;
 0474                        pVal++;
 475                    }
 476
 0477                    if (pVal > pValueLimit)
 478                    {
 479                        // Found match.
 0480                        return (int)(pCurrentSource - pSource);
 481                    }
 482
 0483                    pCurrentSource--;
 484                }
 485
 0486                return -1;
 487            }
 488        }
 489
 490        private static ushort[]?[] InitCasingTable()
 491        {
 0492            ushort[]?[] table = new ushort[]?[s_casingTableInit.Length * 8];
 0493            for (int i = 0; i < s_casingTableInit.Length * 8; ++i)
 494            {
 495                // The bits are in reverse order
 0496                byte val = (byte)(s_casingTableInit[i / 8] >> (7 - (i % 8)));
 0497                if ((val & 1) == 1)
 0498                    table[i] = NoCasingPage;
 499            }
 0500            table[0] = s_basicLatin;
 0501            return table;
 502        }
 503
 504        private static unsafe ushort[] InitOrdinalCasingPage(int pageNumber)
 505        {
 0506            Debug.Assert(pageNumber >= 0 && pageNumber < 256);
 507
 0508            ushort[] casingTable = new ushort[256];
 0509            fixed (ushort* table = casingTable)
 510            {
 0511                char* pTable = (char*)table;
 0512                Interop.Globalization.InitOrdinalCasingPage(pageNumber, pTable);
 513            }
 0514            Volatile.Write(ref s_casingTable[pageNumber], casingTable);
 0515            return casingTable;
 516        }
 517
 518        private static ushort[]?[] InitLowerCasingTable()
 519        {
 520            // Reuse the upper-casing NoCasing bitmap to pre-seed the lower-casing table. Every page that bitmap
 521            // marks (the permanently uncased CJK/Hangul/PUA blocks) is also identity under lower casing, so seeding
 522            // these pages with the shared NoCasingPage avoids first-use native calls without risking a wrong mapping.
 0523            ushort[]?[] table = new ushort[]?[s_casingTableInit.Length * 8];
 0524            for (int i = 0; i < s_casingTableInit.Length * 8; ++i)
 525            {
 526                // The bits are in reverse order
 0527                byte val = (byte)(s_casingTableInit[i / 8] >> (7 - (i % 8)));
 0528                if ((val & 1) == 1)
 0529                    table[i] = NoCasingPage;
 530            }
 0531            table[0] = s_lowerBasicLatin;
 0532            return table;
 533        }
 534
 535        private static unsafe ushort[] InitOrdinalLowerCasingPage(int pageNumber)
 536        {
 0537            Debug.Assert(pageNumber > 0 && pageNumber < 256);
 538
 0539            ushort[] casingTable = new ushort[256];
 0540            fixed (ushort* table = casingTable)
 541            {
 0542                char* pTable = (char*)table;
 0543                Interop.Globalization.InitOrdinalLowerCasingPage(pageNumber, pTable);
 544            }
 545
 546            // If the page doesn't change any character, collapse it to the shared NoCasingPage to avoid
 547            // retaining the 512-byte buffer. We still return the fully populated table for the current lookup.
 0548            int pageBase = pageNumber << 8;
 0549            int i = 0;
 0550            while (i < 256 && casingTable[i] == (ushort)(pageBase + i))
 551            {
 0552                i++;
 553            }
 554
 0555            Volatile.Write(ref s_lowerCasingTable[pageNumber], i == 256 ? NoCasingPage : casingTable);
 0556            return casingTable;
 557        }
 558    }
 559}
 560