| | | 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 | | |
| | | 4 | | using System.Diagnostics; |
| | | 5 | | using System.Runtime.CompilerServices; |
| | | 6 | | using System.Runtime.InteropServices; |
| | | 7 | | using System.Text; |
| | | 8 | | using System.Threading; |
| | | 9 | | |
| | | 10 | | namespace 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. |
| | 0 | 15 | | 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 |
| | 0 | 19 | | private static readonly ushort[] s_basicLatin = |
| | 0 | 20 | | [ |
| | 0 | 21 | | // Upper Casing |
| | 0 | 22 | | |
| | 0 | 23 | | /* 0000-000f */ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x0 |
| | 0 | 24 | | /* 0010-001f */ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x0 |
| | 0 | 25 | | /* 0020-002f */ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x0 |
| | 0 | 26 | | /* 0030-003f */ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x0 |
| | 0 | 27 | | /* 0040-004f */ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x0 |
| | 0 | 28 | | /* 0050-005f */ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x0 |
| | 0 | 29 | | /* 0060-006f */ 0x0060, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x0 |
| | 0 | 30 | | /* 0070-007f */ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x0 |
| | 0 | 31 | | /* 0080-008f */ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008a, 0x0 |
| | 0 | 32 | | /* 0090-009f */ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009a, 0x0 |
| | 0 | 33 | | /* 00a0-00af */ 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x0 |
| | 0 | 34 | | /* 00b0-00bf */ 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x039c, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x0 |
| | 0 | 35 | | /* 00c0-00cf */ 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x0 |
| | 0 | 36 | | /* 00d0-00df */ 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da, 0x0 |
| | 0 | 37 | | /* 00e0-00ef */ 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x0 |
| | 0 | 38 | | /* 00f0-00ff */ 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00f7, 0x00d8, 0x00d9, 0x00da, 0x0 |
| | 0 | 39 | | ]; |
| | | 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. |
| | 0 | 44 | | 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 |
| | 0 | 48 | | private static readonly ushort[] s_lowerBasicLatin = |
| | 0 | 49 | | [ |
| | 0 | 50 | | // Lower Casing |
| | 0 | 51 | | |
| | 0 | 52 | | /* 0000-000f */ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x0 |
| | 0 | 53 | | /* 0010-001f */ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x0 |
| | 0 | 54 | | /* 0020-002f */ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x0 |
| | 0 | 55 | | /* 0030-003f */ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x0 |
| | 0 | 56 | | /* 0040-004f */ 0x0040, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x0 |
| | 0 | 57 | | /* 0050-005f */ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x0 |
| | 0 | 58 | | /* 0060-006f */ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x0 |
| | 0 | 59 | | /* 0070-007f */ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x0 |
| | 0 | 60 | | /* 0080-008f */ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008a, 0x0 |
| | 0 | 61 | | /* 0090-009f */ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009a, 0x0 |
| | 0 | 62 | | /* 00a0-00af */ 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x0 |
| | 0 | 63 | | /* 00b0-00bf */ 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x0 |
| | 0 | 64 | | /* 00c0-00cf */ 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x0 |
| | 0 | 65 | | /* 00d0-00df */ 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00d7, 0x00f8, 0x00f9, 0x00fa, 0x0 |
| | 0 | 66 | | /* 00e0-00ef */ 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x0 |
| | 0 | 67 | | /* 00f0-00ff */ 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x0 |
| | 0 | 68 | | ]; |
| | | 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. |
| | 0 | 73 | | 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 |
| | 0 | 118 | | [ |
| | 0 | 119 | | /* 0000-07FF */ 0b00000000, |
| | 0 | 120 | | /* 0800-0FFF */ 0b00000000, |
| | 0 | 121 | | /* 1000-17FF */ 0b01001100, |
| | 0 | 122 | | /* 1800-1FFF */ 0b00000000, |
| | 0 | 123 | | /* 2000-27FF */ 0b00110111, |
| | 0 | 124 | | /* 2800-2FFF */ 0b11100000, |
| | 0 | 125 | | /* 3000-37FF */ 0b00011111, |
| | 0 | 126 | | /* 3800-3FFF */ 0b11111111, |
| | 0 | 127 | | /* 4000-47FF */ 0b11111111, |
| | 0 | 128 | | /* 4800-4FFF */ 0b11111111, |
| | 0 | 129 | | /* 5000-57FF */ 0b11111111, |
| | 0 | 130 | | /* 5800-5FFF */ 0b11111111, |
| | 0 | 131 | | /* 6000-67FF */ 0b11111111, |
| | 0 | 132 | | /* 6800-6FFF */ 0b11111111, |
| | 0 | 133 | | /* 7000-77FF */ 0b11111111, |
| | 0 | 134 | | /* 7800-7FFF */ 0b11111111, |
| | 0 | 135 | | /* 8000-87FF */ 0b11111111, |
| | 0 | 136 | | /* 8800-8FFF */ 0b11111111, |
| | 0 | 137 | | /* 9000-97FF */ 0b11111111, |
| | 0 | 138 | | /* 9800-9FFF */ 0b11111110, |
| | 0 | 139 | | /* A000-A7FF */ 0b11110100, |
| | 0 | 140 | | /* A800-AFFF */ 0b00001111, |
| | 0 | 141 | | /* B000-B7FF */ 0b11111111, |
| | 0 | 142 | | /* B800-BFFF */ 0b11111111, |
| | 0 | 143 | | /* C000-C7FF */ 0b11111111, |
| | 0 | 144 | | /* C800-CFFF */ 0b11111111, |
| | 0 | 145 | | /* D000-D7FF */ 0b11111110, |
| | 0 | 146 | | /* D800-DFFF */ 0b11111111, |
| | 0 | 147 | | /* E000-E7FF */ 0b11111111, |
| | 0 | 148 | | /* E800-EFFF */ 0b11111111, |
| | 0 | 149 | | /* F000-F7FF */ 0b11111111, |
| | 0 | 150 | | /* F800-FFFF */ 0b11001000, |
| | 0 | 151 | | ]; |
| | | 152 | | |
| | | 153 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 154 | | internal static char ToUpper(char c) |
| | | 155 | | { |
| | 0 | 156 | | int pageNumber = ((int)c) >> 8; |
| | 0 | 157 | | if (pageNumber == 0) // optimize for ASCII range |
| | | 158 | | { |
| | 0 | 159 | | return (char)s_basicLatin[(int)c]; |
| | | 160 | | } |
| | | 161 | | |
| | 0 | 162 | | ushort[]? casingTable = s_casingTable[pageNumber]; |
| | | 163 | | |
| | 0 | 164 | | if (casingTable == NoCasingPage) |
| | | 165 | | { |
| | 0 | 166 | | return c; |
| | | 167 | | } |
| | | 168 | | |
| | 0 | 169 | | casingTable ??= InitOrdinalCasingPage(pageNumber); |
| | | 170 | | |
| | 0 | 171 | | return (char)casingTable[((int)c) & 0xFF]; |
| | | 172 | | } |
| | | 173 | | |
| | | 174 | | internal static void ToUpperOrdinal(ReadOnlySpan<char> source, Span<char> destination) |
| | | 175 | | { |
| | 0 | 176 | | Debug.Assert(!GlobalizationMode.Invariant); |
| | 0 | 177 | | Debug.Assert(!GlobalizationMode.UseNls); |
| | | 178 | | |
| | | 179 | | // Quickly upper-case the leading run of ASCII characters, then handle the remainder. |
| | 0 | 180 | | Ascii.ToUpper(source, destination, out int start); |
| | | 181 | | |
| | 0 | 182 | | for (int i = start; (uint)i < (uint)source.Length; i++) |
| | | 183 | | { |
| | 0 | 184 | | char c = source[i]; |
| | 0 | 185 | | if (c <= '\u00FF') // optimize ASCII/Latin |
| | | 186 | | { |
| | 0 | 187 | | destination[i] = (char)s_basicLatin[c]; |
| | 0 | 188 | | continue; |
| | | 189 | | } |
| | | 190 | | |
| | 0 | 191 | | if (char.IsHighSurrogate(c) && i < source.Length - 1) |
| | | 192 | | { |
| | 0 | 193 | | char cl = source[i + 1]; |
| | 0 | 194 | | if (char.IsLowSurrogate(cl)) |
| | | 195 | | { |
| | | 196 | | // well formed surrogates |
| | 0 | 197 | | SurrogateCasing.ToUpper(c, cl, out destination[i], out destination[i + 1]); |
| | 0 | 198 | | i++; // skip the low surrogate |
| | 0 | 199 | | continue; |
| | | 200 | | } |
| | | 201 | | } |
| | | 202 | | |
| | 0 | 203 | | destination[i] = ToUpper(c); |
| | | 204 | | } |
| | 0 | 205 | | } |
| | | 206 | | |
| | | 207 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 208 | | internal static char ToLower(char c) |
| | | 209 | | { |
| | 0 | 210 | | int pageNumber = ((int)c) >> 8; |
| | 0 | 211 | | if (pageNumber == 0) // optimize for ASCII range |
| | | 212 | | { |
| | 0 | 213 | | return (char)s_lowerBasicLatin[(int)c]; |
| | | 214 | | } |
| | | 215 | | |
| | 0 | 216 | | ushort[]? casingTable = s_lowerCasingTable[pageNumber]; |
| | | 217 | | |
| | 0 | 218 | | if (casingTable == NoCasingPage) |
| | | 219 | | { |
| | 0 | 220 | | return c; |
| | | 221 | | } |
| | | 222 | | |
| | 0 | 223 | | casingTable ??= InitOrdinalLowerCasingPage(pageNumber); |
| | | 224 | | |
| | 0 | 225 | | return (char)casingTable[((int)c) & 0xFF]; |
| | | 226 | | } |
| | | 227 | | |
| | | 228 | | internal static void ToLowerOrdinal(ReadOnlySpan<char> source, Span<char> destination) |
| | | 229 | | { |
| | 0 | 230 | | Debug.Assert(!GlobalizationMode.Invariant); |
| | 0 | 231 | | Debug.Assert(!GlobalizationMode.UseNls); |
| | | 232 | | |
| | | 233 | | // Quickly lower-case the leading run of ASCII characters, then handle the remainder. |
| | 0 | 234 | | Ascii.ToLower(source, destination, out int start); |
| | | 235 | | |
| | 0 | 236 | | for (int i = start; (uint)i < (uint)source.Length; i++) |
| | | 237 | | { |
| | 0 | 238 | | char c = source[i]; |
| | 0 | 239 | | if (c <= '\u00FF') // optimize ASCII/Latin |
| | | 240 | | { |
| | 0 | 241 | | destination[i] = (char)s_lowerBasicLatin[c]; |
| | 0 | 242 | | continue; |
| | | 243 | | } |
| | | 244 | | |
| | 0 | 245 | | if (char.IsHighSurrogate(c) && i < source.Length - 1) |
| | | 246 | | { |
| | 0 | 247 | | char cl = source[i + 1]; |
| | 0 | 248 | | if (char.IsLowSurrogate(cl)) |
| | | 249 | | { |
| | | 250 | | // well formed surrogates |
| | 0 | 251 | | SurrogateCasing.ToLower(c, cl, out destination[i], out destination[i + 1]); |
| | 0 | 252 | | i++; // skip the low surrogate |
| | 0 | 253 | | continue; |
| | | 254 | | } |
| | | 255 | | } |
| | | 256 | | |
| | 0 | 257 | | destination[i] = ToLower(c); |
| | | 258 | | } |
| | 0 | 259 | | } |
| | | 260 | | |
| | | 261 | | internal static int CompareStringIgnoreCase(ref char strA, int lengthA, ref char strB, int lengthB) |
| | | 262 | | { |
| | 0 | 263 | | Debug.Assert(!GlobalizationMode.Invariant); |
| | 0 | 264 | | Debug.Assert(!GlobalizationMode.UseNls); |
| | | 265 | | |
| | 0 | 266 | | int length = Math.Min(lengthA, lengthB); |
| | | 267 | | |
| | 0 | 268 | | ref char charA = ref strA; |
| | 0 | 269 | | ref char charB = ref strB; |
| | | 270 | | |
| | 0 | 271 | | int index = 0; |
| | | 272 | | |
| | 0 | 273 | | while (index < length) |
| | | 274 | | { |
| | 0 | 275 | | char a = charA; |
| | 0 | 276 | | char b = charB; |
| | 0 | 277 | | char lowSurrogateA = '\0'; |
| | | 278 | | |
| | 0 | 279 | | if (!char.IsHighSurrogate(a) || index >= lengthA - 1 || !char.IsLowSurrogate(lowSurrogateA = Unsafe.Add( |
| | | 280 | | { |
| | 0 | 281 | | 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 | | |
| | 0 | 287 | | if (b == a) |
| | | 288 | | { |
| | 0 | 289 | | index++; |
| | 0 | 290 | | charA = ref Unsafe.Add(ref charA, 1); |
| | 0 | 291 | | charB = ref Unsafe.Add(ref charB, 1); |
| | 0 | 292 | | continue; |
| | | 293 | | } |
| | | 294 | | |
| | 0 | 295 | | char aUpper = ToUpper(a); |
| | 0 | 296 | | char bUpper = ToUpper(b); |
| | | 297 | | |
| | 0 | 298 | | if (aUpper == bUpper) |
| | | 299 | | { |
| | 0 | 300 | | index++; |
| | 0 | 301 | | charA = ref Unsafe.Add(ref charA, 1); |
| | 0 | 302 | | charB = ref Unsafe.Add(ref charB, 1); |
| | 0 | 303 | | continue; |
| | | 304 | | } |
| | | 305 | | |
| | 0 | 306 | | return aUpper - bUpper; |
| | | 307 | | } |
| | | 308 | | |
| | | 309 | | // |
| | | 310 | | // charA is not surrogate and charB is valid surrogate |
| | | 311 | | // |
| | | 312 | | |
| | 0 | 313 | | return -1; |
| | | 314 | | } |
| | | 315 | | |
| | | 316 | | // |
| | | 317 | | // A is Surrogate |
| | | 318 | | // |
| | | 319 | | |
| | 0 | 320 | | char lowSurrogateB = '\0'; |
| | | 321 | | |
| | 0 | 322 | | 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 | | |
| | 0 | 328 | | return 1; |
| | | 329 | | } |
| | | 330 | | |
| | | 331 | | // |
| | | 332 | | // charA and charB are surrogates |
| | | 333 | | // |
| | | 334 | | |
| | 0 | 335 | | Debug.Assert(lowSurrogateA != '\0'); |
| | 0 | 336 | | Debug.Assert(lowSurrogateB != '\0'); |
| | | 337 | | |
| | 0 | 338 | | if (a == b && lowSurrogateA == lowSurrogateB) |
| | | 339 | | { |
| | 0 | 340 | | index += 2; |
| | 0 | 341 | | charA = ref Unsafe.Add(ref charA, 2); |
| | 0 | 342 | | charB = ref Unsafe.Add(ref charB, 2); |
| | 0 | 343 | | continue; |
| | | 344 | | } |
| | | 345 | | |
| | 0 | 346 | | uint upperSurrogateA = CharUnicodeInfo.ToUpper(UnicodeUtility.GetScalarFromUtf16SurrogatePair(a, lowSurr |
| | 0 | 347 | | uint upperSurrogateB = CharUnicodeInfo.ToUpper(UnicodeUtility.GetScalarFromUtf16SurrogatePair(b, lowSurr |
| | | 348 | | |
| | 0 | 349 | | if (upperSurrogateA == upperSurrogateB) |
| | | 350 | | { |
| | 0 | 351 | | index += 2; |
| | 0 | 352 | | charA = ref Unsafe.Add(ref charA, 2); |
| | 0 | 353 | | charB = ref Unsafe.Add(ref charB, 2); |
| | 0 | 354 | | continue; |
| | | 355 | | } |
| | | 356 | | |
| | 0 | 357 | | return (int)upperSurrogateA - (int)upperSurrogateB; |
| | | 358 | | } |
| | | 359 | | |
| | 0 | 360 | | return lengthA - lengthB; |
| | | 361 | | } |
| | | 362 | | |
| | | 363 | | internal static unsafe int IndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value) |
| | | 364 | | { |
| | 0 | 365 | | Debug.Assert(value.Length > 0); |
| | 0 | 366 | | Debug.Assert(value.Length <= source.Length); |
| | | 367 | | |
| | 0 | 368 | | Debug.Assert(!GlobalizationMode.Invariant); |
| | 0 | 369 | | Debug.Assert(!GlobalizationMode.UseNls); |
| | | 370 | | |
| | 0 | 371 | | fixed (char* pSource = &MemoryMarshal.GetReference(source)) |
| | 0 | 372 | | fixed (char* pValue = &MemoryMarshal.GetReference(value)) |
| | | 373 | | { |
| | 0 | 374 | | char* pSourceLimit = pSource + (source.Length - value.Length); |
| | 0 | 375 | | char* pValueLimit = pValue + value.Length - 1; |
| | 0 | 376 | | char* pCurrentSource = pSource; |
| | | 377 | | |
| | 0 | 378 | | while (pCurrentSource <= pSourceLimit) |
| | | 379 | | { |
| | 0 | 380 | | char *pVal = pValue; |
| | 0 | 381 | | char *pSrc = pCurrentSource; |
| | | 382 | | |
| | 0 | 383 | | while (pVal <= pValueLimit) |
| | | 384 | | { |
| | 0 | 385 | | if (!char.IsHighSurrogate(*pVal) || pVal == pValueLimit) |
| | | 386 | | { |
| | 0 | 387 | | if (*pVal != *pSrc && ToUpper(*pVal) != ToUpper(*pSrc)) |
| | | 388 | | break; // no match |
| | | 389 | | |
| | 0 | 390 | | pVal++; |
| | 0 | 391 | | pSrc++; |
| | 0 | 392 | | continue; |
| | | 393 | | } |
| | | 394 | | |
| | 0 | 395 | | 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. |
| | 0 | 399 | | if (!SurrogateCasing.Equal(*pSrc, *(pSrc + 1), *pVal, *(pVal + 1))) |
| | | 400 | | break; // no match |
| | | 401 | | |
| | 0 | 402 | | pSrc += 2; |
| | 0 | 403 | | pVal += 2; |
| | 0 | 404 | | continue; |
| | | 405 | | } |
| | | 406 | | |
| | 0 | 407 | | if (*pVal != *pSrc) |
| | | 408 | | break; // no match |
| | | 409 | | |
| | 0 | 410 | | pSrc++; |
| | 0 | 411 | | pVal++; |
| | | 412 | | } |
| | | 413 | | |
| | 0 | 414 | | if (pVal > pValueLimit) |
| | | 415 | | { |
| | | 416 | | // Found match. |
| | 0 | 417 | | return (int) (pCurrentSource - pSource); |
| | | 418 | | } |
| | | 419 | | |
| | 0 | 420 | | pCurrentSource++; |
| | | 421 | | } |
| | | 422 | | |
| | 0 | 423 | | return -1; |
| | | 424 | | } |
| | | 425 | | } |
| | | 426 | | |
| | | 427 | | internal static unsafe int LastIndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value) |
| | | 428 | | { |
| | 0 | 429 | | Debug.Assert(value.Length > 0); |
| | 0 | 430 | | Debug.Assert(value.Length <= source.Length); |
| | | 431 | | |
| | 0 | 432 | | Debug.Assert(!GlobalizationMode.Invariant); |
| | 0 | 433 | | Debug.Assert(!GlobalizationMode.UseNls); |
| | | 434 | | |
| | 0 | 435 | | fixed (char* pSource = &MemoryMarshal.GetReference(source)) |
| | 0 | 436 | | fixed (char* pValue = &MemoryMarshal.GetReference(value)) |
| | | 437 | | { |
| | 0 | 438 | | char* pValueLimit = pValue + value.Length - 1; |
| | 0 | 439 | | char* pCurrentSource = pSource + (source.Length - value.Length); |
| | | 440 | | |
| | 0 | 441 | | while (pCurrentSource >= pSource) |
| | | 442 | | { |
| | 0 | 443 | | char *pVal = pValue; |
| | 0 | 444 | | char *pSrc = pCurrentSource; |
| | | 445 | | |
| | 0 | 446 | | while (pVal <= pValueLimit) |
| | | 447 | | { |
| | 0 | 448 | | if (!char.IsHighSurrogate(*pVal) || pVal == pValueLimit) |
| | | 449 | | { |
| | 0 | 450 | | if (*pVal != *pSrc && ToUpper(*pVal) != ToUpper(*pSrc)) |
| | | 451 | | break; // no match |
| | | 452 | | |
| | 0 | 453 | | pVal++; |
| | 0 | 454 | | pSrc++; |
| | 0 | 455 | | continue; |
| | | 456 | | } |
| | | 457 | | |
| | 0 | 458 | | 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. |
| | 0 | 462 | | if (!SurrogateCasing.Equal(*pSrc, *(pSrc + 1), *pVal, *(pVal + 1))) |
| | | 463 | | break; // no match |
| | | 464 | | |
| | 0 | 465 | | pSrc += 2; |
| | 0 | 466 | | pVal += 2; |
| | 0 | 467 | | continue; |
| | | 468 | | } |
| | | 469 | | |
| | 0 | 470 | | if (*pVal != *pSrc) |
| | | 471 | | break; // no match |
| | | 472 | | |
| | 0 | 473 | | pSrc++; |
| | 0 | 474 | | pVal++; |
| | | 475 | | } |
| | | 476 | | |
| | 0 | 477 | | if (pVal > pValueLimit) |
| | | 478 | | { |
| | | 479 | | // Found match. |
| | 0 | 480 | | return (int)(pCurrentSource - pSource); |
| | | 481 | | } |
| | | 482 | | |
| | 0 | 483 | | pCurrentSource--; |
| | | 484 | | } |
| | | 485 | | |
| | 0 | 486 | | return -1; |
| | | 487 | | } |
| | | 488 | | } |
| | | 489 | | |
| | | 490 | | private static ushort[]?[] InitCasingTable() |
| | | 491 | | { |
| | 0 | 492 | | ushort[]?[] table = new ushort[]?[s_casingTableInit.Length * 8]; |
| | 0 | 493 | | for (int i = 0; i < s_casingTableInit.Length * 8; ++i) |
| | | 494 | | { |
| | | 495 | | // The bits are in reverse order |
| | 0 | 496 | | byte val = (byte)(s_casingTableInit[i / 8] >> (7 - (i % 8))); |
| | 0 | 497 | | if ((val & 1) == 1) |
| | 0 | 498 | | table[i] = NoCasingPage; |
| | | 499 | | } |
| | 0 | 500 | | table[0] = s_basicLatin; |
| | 0 | 501 | | return table; |
| | | 502 | | } |
| | | 503 | | |
| | | 504 | | private static unsafe ushort[] InitOrdinalCasingPage(int pageNumber) |
| | | 505 | | { |
| | 0 | 506 | | Debug.Assert(pageNumber >= 0 && pageNumber < 256); |
| | | 507 | | |
| | 0 | 508 | | ushort[] casingTable = new ushort[256]; |
| | 0 | 509 | | fixed (ushort* table = casingTable) |
| | | 510 | | { |
| | 0 | 511 | | char* pTable = (char*)table; |
| | 0 | 512 | | Interop.Globalization.InitOrdinalCasingPage(pageNumber, pTable); |
| | | 513 | | } |
| | 0 | 514 | | Volatile.Write(ref s_casingTable[pageNumber], casingTable); |
| | 0 | 515 | | 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. |
| | 0 | 523 | | ushort[]?[] table = new ushort[]?[s_casingTableInit.Length * 8]; |
| | 0 | 524 | | for (int i = 0; i < s_casingTableInit.Length * 8; ++i) |
| | | 525 | | { |
| | | 526 | | // The bits are in reverse order |
| | 0 | 527 | | byte val = (byte)(s_casingTableInit[i / 8] >> (7 - (i % 8))); |
| | 0 | 528 | | if ((val & 1) == 1) |
| | 0 | 529 | | table[i] = NoCasingPage; |
| | | 530 | | } |
| | 0 | 531 | | table[0] = s_lowerBasicLatin; |
| | 0 | 532 | | return table; |
| | | 533 | | } |
| | | 534 | | |
| | | 535 | | private static unsafe ushort[] InitOrdinalLowerCasingPage(int pageNumber) |
| | | 536 | | { |
| | 0 | 537 | | Debug.Assert(pageNumber > 0 && pageNumber < 256); |
| | | 538 | | |
| | 0 | 539 | | ushort[] casingTable = new ushort[256]; |
| | 0 | 540 | | fixed (ushort* table = casingTable) |
| | | 541 | | { |
| | 0 | 542 | | char* pTable = (char*)table; |
| | 0 | 543 | | 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. |
| | 0 | 548 | | int pageBase = pageNumber << 8; |
| | 0 | 549 | | int i = 0; |
| | 0 | 550 | | while (i < 256 && casingTable[i] == (ushort)(pageBase + i)) |
| | | 551 | | { |
| | 0 | 552 | | i++; |
| | | 553 | | } |
| | | 554 | | |
| | 0 | 555 | | Volatile.Write(ref s_lowerCasingTable[pageNumber], i == 256 ? NoCasingPage : casingTable); |
| | 0 | 556 | | return casingTable; |
| | | 557 | | } |
| | | 558 | | } |
| | | 559 | | } |
| | | 560 | | |