| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- // See the LICENSE file in the project root for more information.
- using System.Buffers;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Runtime.CompilerServices;
- using System.Runtime.InteropServices;
- using System.Security;
- using System.Threading;
- using Internal.Runtime.CompilerServices;
- namespace System.Globalization
- {
- public partial class CompareInfo
- {
- [NonSerialized]
- private IntPtr _sortHandle;
- [NonSerialized]
- private bool _isAsciiEqualityOrdinal;
- private void InitSort(CultureInfo culture)
- {
- _sortName = culture.SortName;
- if (GlobalizationMode.Invariant)
- {
- _isAsciiEqualityOrdinal = true;
- }
- else
- {
- _isAsciiEqualityOrdinal = (_sortName == "en-US" || _sortName == "");
- _sortHandle = SortHandleCache.GetCachedSortHandle(_sortName);
- }
- }
- internal static unsafe int IndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(source != null);
- Debug.Assert(value != null);
- if (value.Length == 0)
- {
- return startIndex;
- }
- if (count < value.Length)
- {
- return -1;
- }
- if (ignoreCase)
- {
- fixed (char* pSource = source)
- {
- int index = Interop.Globalization.IndexOfOrdinalIgnoreCase(value, value.Length, pSource + startIndex, count, findLast: false);
- return index != -1 ?
- startIndex + index :
- -1;
- }
- }
- int endIndex = startIndex + (count - value.Length);
- for (int i = startIndex; i <= endIndex; i++)
- {
- int valueIndex, sourceIndex;
- for (valueIndex = 0, sourceIndex = i;
- valueIndex < value.Length && source[sourceIndex] == value[valueIndex];
- valueIndex++, sourceIndex++) ;
- if (valueIndex == value.Length)
- {
- return i;
- }
- }
- return -1;
- }
- internal static unsafe int IndexOfOrdinalCore(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase, bool fromBeginning)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(source.Length != 0);
- Debug.Assert(value.Length != 0);
- if (source.Length < value.Length)
- {
- return -1;
- }
- if (ignoreCase)
- {
- fixed (char* pSource = &MemoryMarshal.GetReference(source))
- fixed (char* pValue = &MemoryMarshal.GetReference(value))
- {
- return Interop.Globalization.IndexOfOrdinalIgnoreCase(pValue, value.Length, pSource, source.Length, findLast: !fromBeginning);
- }
- }
- int startIndex, endIndex, jump;
- if (fromBeginning)
- {
- // Left to right, from zero to last possible index in the source string.
- // Incrementing by one after each iteration. Stop condition is last possible index plus 1.
- startIndex = 0;
- endIndex = source.Length - value.Length + 1;
- jump = 1;
- }
- else
- {
- // Right to left, from first possible index in the source string to zero.
- // Decrementing by one after each iteration. Stop condition is last possible index minus 1.
- startIndex = source.Length - value.Length;
- endIndex = -1;
- jump = -1;
- }
- for (int i = startIndex; i != endIndex; i += jump)
- {
- int valueIndex, sourceIndex;
- for (valueIndex = 0, sourceIndex = i;
- valueIndex < value.Length && source[sourceIndex] == value[valueIndex];
- valueIndex++, sourceIndex++)
- ;
- if (valueIndex == value.Length)
- {
- return i;
- }
- }
- return -1;
- }
- internal static unsafe int LastIndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(source != null);
- Debug.Assert(value != null);
- if (value.Length == 0)
- {
- return startIndex;
- }
- if (count < value.Length)
- {
- return -1;
- }
- // startIndex is the index into source where we start search backwards from.
- // leftStartIndex is the index into source of the start of the string that is
- // count characters away from startIndex.
- int leftStartIndex = startIndex - count + 1;
- if (ignoreCase)
- {
- fixed (char* pSource = source)
- {
- int lastIndex = Interop.Globalization.IndexOfOrdinalIgnoreCase(value, value.Length, pSource + leftStartIndex, count, findLast: true);
- return lastIndex != -1 ?
- leftStartIndex + lastIndex :
- -1;
- }
- }
- for (int i = startIndex - value.Length + 1; i >= leftStartIndex; i--)
- {
- int valueIndex, sourceIndex;
- for (valueIndex = 0, sourceIndex = i;
- valueIndex < value.Length && source[sourceIndex] == value[valueIndex];
- valueIndex++, sourceIndex++) ;
- if (valueIndex == value.Length) {
- return i;
- }
- }
- return -1;
- }
- private static unsafe int CompareStringOrdinalIgnoreCase(ref char string1, int count1, ref char string2, int count2)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- fixed (char* char1 = &string1)
- fixed (char* char2 = &string2)
- {
- return Interop.Globalization.CompareStringOrdinalIgnoreCase(char1, count1, char2, count2);
- }
- }
- // TODO https://github.com/dotnet/coreclr/issues/13827:
- // This method shouldn't be necessary, as we should be able to just use the overload
- // that takes two spans. But due to this issue, that's adding significant overhead.
- private unsafe int CompareString(ReadOnlySpan<char> string1, string string2, CompareOptions options)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(string2 != null);
- Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
- fixed (char* pString1 = &MemoryMarshal.GetReference(string1))
- fixed (char* pString2 = &string2.GetRawStringData())
- {
- return Interop.Globalization.CompareString(_sortHandle, pString1, string1.Length, pString2, string2.Length, options);
- }
- }
- private unsafe int CompareString(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2, CompareOptions options)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
- fixed (char* pString1 = &MemoryMarshal.GetReference(string1))
- fixed (char* pString2 = &MemoryMarshal.GetReference(string2))
- {
- return Interop.Globalization.CompareString(_sortHandle, pString1, string1.Length, pString2, string2.Length, options);
- }
- }
- internal unsafe int IndexOfCore(string source, string target, int startIndex, int count, CompareOptions options, int* matchLengthPtr)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(!string.IsNullOrEmpty(source));
- Debug.Assert(target != null);
- Debug.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0);
- Debug.Assert((options & CompareOptions.Ordinal) == 0);
- #if CORECLR
- if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options) && source.IsFastSort() && target.IsFastSort())
- {
- int index = IndexOf(source, target, startIndex, count, GetOrdinalCompareOptions(options));
- if (index != -1)
- {
- if (matchLengthPtr != null)
- *matchLengthPtr = target.Length;
- }
- return index;
- }
- #endif
- fixed (char* pSource = source)
- fixed (char* pTarget = target)
- {
- int index = Interop.Globalization.IndexOf(_sortHandle, pTarget, target.Length, pSource + startIndex, count, options, matchLengthPtr);
- return index != -1 ? index + startIndex : -1;
- }
- }
- // For now, this method is only called from Span APIs with either options == CompareOptions.None or CompareOptions.IgnoreCase
- internal unsafe int IndexOfCore(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr, bool fromBeginning)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(source.Length != 0);
- Debug.Assert(target.Length != 0);
- if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options))
- {
- if ((options & CompareOptions.IgnoreCase) == CompareOptions.IgnoreCase)
- return IndexOfOrdinalIgnoreCaseHelper(source, target, options, matchLengthPtr, fromBeginning);
- else
- return IndexOfOrdinalHelper(source, target, options, matchLengthPtr, fromBeginning);
- }
- else
- {
- fixed (char* pSource = &MemoryMarshal.GetReference(source))
- fixed (char* pTarget = &MemoryMarshal.GetReference(target))
- {
- if (fromBeginning)
- return Interop.Globalization.IndexOf(_sortHandle, pTarget, target.Length, pSource, source.Length, options, matchLengthPtr);
- else
- return Interop.Globalization.LastIndexOf(_sortHandle, pTarget, target.Length, pSource, source.Length, options);
- }
- }
- }
- /// <summary>
- /// Duplicate of IndexOfOrdinalHelper that also handles ignore case. Can't converge both methods
- /// as the JIT wouldn't be able to optimize the ignoreCase path away.
- /// </summary>
- /// <returns></returns>
- private unsafe int IndexOfOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr, bool fromBeginning)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(!source.IsEmpty);
- Debug.Assert(!target.IsEmpty);
- Debug.Assert(_isAsciiEqualityOrdinal);
- fixed (char* ap = &MemoryMarshal.GetReference(source))
- fixed (char* bp = &MemoryMarshal.GetReference(target))
- {
- char* a = ap;
- char* b = bp;
- if (target.Length > source.Length)
- goto InteropCall;
- for (int j = 0; j < target.Length; j++)
- {
- char targetChar = *(b + j);
- if (targetChar >= 0x80 || s_highCharTable[targetChar])
- goto InteropCall;
- }
- int startIndex, endIndex, jump;
- if (fromBeginning)
- {
- // Left to right, from zero to last possible index in the source string.
- // Incrementing by one after each iteration. Stop condition is last possible index plus 1.
- startIndex = 0;
- endIndex = source.Length - target.Length + 1;
- jump = 1;
- }
- else
- {
- // Right to left, from first possible index in the source string to zero.
- // Decrementing by one after each iteration. Stop condition is last possible index minus 1.
- startIndex = source.Length - target.Length;
- endIndex = -1;
- jump = -1;
- }
- for (int i = startIndex; i != endIndex; i += jump)
- {
- int targetIndex = 0;
- int sourceIndex = i;
- for (; targetIndex < target.Length; targetIndex++, sourceIndex++)
- {
- char valueChar = *(a + sourceIndex);
- char targetChar = *(b + targetIndex);
- if (valueChar == targetChar && valueChar < 0x80 && !s_highCharTable[valueChar])
- {
- continue;
- }
- // uppercase both chars - notice that we need just one compare per char
- if ((uint)(valueChar - 'a') <= ('z' - 'a'))
- valueChar = (char)(valueChar - 0x20);
- if ((uint)(targetChar - 'a') <= ('z' - 'a'))
- targetChar = (char)(targetChar - 0x20);
- if (valueChar >= 0x80 || s_highCharTable[valueChar])
- goto InteropCall;
- else if (valueChar != targetChar)
- break;
- }
- if (targetIndex == target.Length)
- {
- if (matchLengthPtr != null)
- *matchLengthPtr = target.Length;
- return i;
- }
- }
- return -1;
- InteropCall:
- if (fromBeginning)
- return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr);
- else
- return Interop.Globalization.LastIndexOf(_sortHandle, b, target.Length, a, source.Length, options);
- }
- }
- private unsafe int IndexOfOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr, bool fromBeginning)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(!source.IsEmpty);
- Debug.Assert(!target.IsEmpty);
- Debug.Assert(_isAsciiEqualityOrdinal);
- fixed (char* ap = &MemoryMarshal.GetReference(source))
- fixed (char* bp = &MemoryMarshal.GetReference(target))
- {
- char* a = ap;
- char* b = bp;
- if (target.Length > source.Length)
- goto InteropCall;
- for (int j = 0; j < target.Length; j++)
- {
- char targetChar = *(b + j);
- if (targetChar >= 0x80 || s_highCharTable[targetChar])
- goto InteropCall;
- }
- int startIndex, endIndex, jump;
- if (fromBeginning)
- {
- // Left to right, from zero to last possible index in the source string.
- // Incrementing by one after each iteration. Stop condition is last possible index plus 1.
- startIndex = 0;
- endIndex = source.Length - target.Length + 1;
- jump = 1;
- }
- else
- {
- // Right to left, from first possible index in the source string to zero.
- // Decrementing by one after each iteration. Stop condition is last possible index minus 1.
- startIndex = source.Length - target.Length;
- endIndex = -1;
- jump = -1;
- }
- for (int i = startIndex; i != endIndex; i += jump)
- {
- int targetIndex = 0;
- int sourceIndex = i;
- for (; targetIndex < target.Length; targetIndex++, sourceIndex++)
- {
- char valueChar = *(a + sourceIndex);
- char targetChar = *(b + targetIndex);
- if (valueChar >= 0x80 || s_highCharTable[valueChar])
- goto InteropCall;
- else if (valueChar != targetChar)
- break;
- }
- if (targetIndex == target.Length)
- {
- if (matchLengthPtr != null)
- *matchLengthPtr = target.Length;
- return i;
- }
- }
- return -1;
- InteropCall:
- if (fromBeginning)
- return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr);
- else
- return Interop.Globalization.LastIndexOf(_sortHandle, b, target.Length, a, source.Length, options);
- }
- }
- private unsafe int LastIndexOfCore(string source, string target, int startIndex, int count, CompareOptions options)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(!string.IsNullOrEmpty(source));
- Debug.Assert(target != null);
- Debug.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0);
- if (target.Length == 0)
- {
- return startIndex;
- }
- if (options == CompareOptions.Ordinal)
- {
- return LastIndexOfOrdinalCore(source, target, startIndex, count, ignoreCase: false);
- }
- #if CORECLR
- if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options) && source.IsFastSort() && target.IsFastSort())
- {
- return LastIndexOf(source, target, startIndex, count, GetOrdinalCompareOptions(options));
- }
- #endif
- // startIndex is the index into source where we start search backwards from. leftStartIndex is the index into source
- // of the start of the string that is count characters away from startIndex.
- int leftStartIndex = (startIndex - count + 1);
- fixed (char* pSource = source)
- fixed (char* pTarget = target)
- {
- int lastIndex = Interop.Globalization.LastIndexOf(_sortHandle, pTarget, target.Length, pSource + (startIndex - count + 1), count, options);
- return lastIndex != -1 ? lastIndex + leftStartIndex : -1;
- }
- }
- private bool StartsWith(string source, string prefix, CompareOptions options)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(!string.IsNullOrEmpty(source));
- Debug.Assert(!string.IsNullOrEmpty(prefix));
- Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
- #if CORECLR
- if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options) && source.IsFastSort() && prefix.IsFastSort())
- {
- return IsPrefix(source, prefix, GetOrdinalCompareOptions(options));
- }
- #endif
- return Interop.Globalization.StartsWith(_sortHandle, prefix, prefix.Length, source, source.Length, options);
- }
- private unsafe bool StartsWith(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(!source.IsEmpty);
- Debug.Assert(!prefix.IsEmpty);
- Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
- if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options))
- {
- if (source.Length < prefix.Length)
- {
- return false;
- }
- if ((options & CompareOptions.IgnoreCase) == CompareOptions.IgnoreCase)
- {
- return StartsWithOrdinalIgnoreCaseHelper(source, prefix, options);
- }
- else
- {
- return StartsWithOrdinalHelper(source, prefix, options);
- }
- }
- else
- {
- fixed (char* pSource = &MemoryMarshal.GetReference(source))
- fixed (char* pPrefix = &MemoryMarshal.GetReference(prefix))
- {
- return Interop.Globalization.StartsWith(_sortHandle, pPrefix, prefix.Length, pSource, source.Length, options);
- }
- }
- }
- private unsafe bool StartsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(!source.IsEmpty);
- Debug.Assert(!prefix.IsEmpty);
- Debug.Assert(_isAsciiEqualityOrdinal);
- Debug.Assert(source.Length >= prefix.Length);
- int length = prefix.Length;
- fixed (char* ap = &MemoryMarshal.GetReference(source))
- fixed (char* bp = &MemoryMarshal.GetReference(prefix))
- {
- char* a = ap;
- char* b = bp;
- while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
- {
- int charA = *a;
- int charB = *b;
- if (charA == charB)
- {
- a++; b++;
- length--;
- continue;
- }
- // uppercase both chars - notice that we need just one compare per char
- if ((uint)(charA - 'a') <= (uint)('z' - 'a')) charA -= 0x20;
- if ((uint)(charB - 'a') <= (uint)('z' - 'a')) charB -= 0x20;
- if (charA != charB)
- return false;
- // Next char
- a++; b++;
- length--;
- }
- if (length == 0) return true;
- return Interop.Globalization.StartsWith(_sortHandle, b, length, a, length, options);
- }
- }
- private unsafe bool StartsWithOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(!source.IsEmpty);
- Debug.Assert(!prefix.IsEmpty);
- Debug.Assert(_isAsciiEqualityOrdinal);
- Debug.Assert(source.Length >= prefix.Length);
- int length = prefix.Length;
- fixed (char* ap = &MemoryMarshal.GetReference(source))
- fixed (char* bp = &MemoryMarshal.GetReference(prefix))
- {
- char* a = ap;
- char* b = bp;
- while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
- {
- int charA = *a;
- int charB = *b;
- if (charA != charB)
- return false;
- // Next char
- a++; b++;
- length--;
- }
- if (length == 0) return true;
- return Interop.Globalization.StartsWith(_sortHandle, b, length, a, length, options);
- }
- }
- private bool EndsWith(string source, string suffix, CompareOptions options)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(!string.IsNullOrEmpty(source));
- Debug.Assert(!string.IsNullOrEmpty(suffix));
- Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
- #if CORECLR
- if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options) && source.IsFastSort() && suffix.IsFastSort())
- {
- return IsSuffix(source, suffix, GetOrdinalCompareOptions(options));
- }
- #endif
- return Interop.Globalization.EndsWith(_sortHandle, suffix, suffix.Length, source, source.Length, options);
- }
- private unsafe bool EndsWith(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(!source.IsEmpty);
- Debug.Assert(!suffix.IsEmpty);
- Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
- if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options))
- {
- if (source.Length < suffix.Length)
- {
- return false;
- }
- if ((options & CompareOptions.IgnoreCase) == CompareOptions.IgnoreCase)
- {
- return EndsWithOrdinalIgnoreCaseHelper(source, suffix, options);
- }
- else
- {
- return EndsWithOrdinalHelper(source, suffix, options);
- }
- }
- else
- {
- fixed (char* pSource = &MemoryMarshal.GetReference(source))
- fixed (char* pSuffix = &MemoryMarshal.GetReference(suffix))
- {
- return Interop.Globalization.EndsWith(_sortHandle, pSuffix, suffix.Length, pSource, source.Length, options);
- }
- }
- }
- private unsafe bool EndsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(!source.IsEmpty);
- Debug.Assert(!suffix.IsEmpty);
- Debug.Assert(_isAsciiEqualityOrdinal);
- Debug.Assert(source.Length >= suffix.Length);
- int length = suffix.Length;
- fixed (char* ap = &MemoryMarshal.GetReference(source))
- fixed (char* bp = &MemoryMarshal.GetReference(suffix))
- {
- char* a = ap + source.Length - 1;
- char* b = bp + suffix.Length - 1;
- while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
- {
- int charA = *a;
- int charB = *b;
- if (charA == charB)
- {
- a--; b--;
- length--;
- continue;
- }
- // uppercase both chars - notice that we need just one compare per char
- if ((uint)(charA - 'a') <= (uint)('z' - 'a')) charA -= 0x20;
- if ((uint)(charB - 'a') <= (uint)('z' - 'a')) charB -= 0x20;
- if (charA != charB)
- return false;
- // Next char
- a--; b--;
- length--;
- }
- if (length == 0) return true;
- return Interop.Globalization.EndsWith(_sortHandle, b - length + 1, length, a - length + 1, length, options);
- }
- }
- private unsafe bool EndsWithOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert(!source.IsEmpty);
- Debug.Assert(!suffix.IsEmpty);
- Debug.Assert(_isAsciiEqualityOrdinal);
- Debug.Assert(source.Length >= suffix.Length);
- int length = suffix.Length;
- fixed (char* ap = &MemoryMarshal.GetReference(source))
- fixed (char* bp = &MemoryMarshal.GetReference(suffix))
- {
- char* a = ap + source.Length - 1;
- char* b = bp + suffix.Length - 1;
- while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
- {
- int charA = *a;
- int charB = *b;
- if (charA != charB)
- return false;
- // Next char
- a--; b--;
- length--;
- }
- if (length == 0) return true;
- return Interop.Globalization.EndsWith(_sortHandle, b - length + 1, length, a - length + 1, length, options);
- }
- }
- private unsafe SortKey CreateSortKey(string source, CompareOptions options)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- if (source==null) { throw new ArgumentNullException(nameof(source)); }
- if ((options & ValidSortkeyCtorMaskOffFlags) != 0)
- {
- throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
- }
- byte [] keyData;
- if (source.Length == 0)
- {
- keyData = Array.Empty<Byte>();
- }
- else
- {
- fixed (char* pSource = source)
- {
- int sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, null, 0, options);
- keyData = new byte[sortKeyLength];
- fixed (byte* pSortKey = keyData)
- {
- if (Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, pSortKey, sortKeyLength, options) != sortKeyLength)
- {
- throw new ArgumentException(SR.Arg_ExternalException);
- }
- }
- }
- }
- return new SortKey(Name, source, options, keyData);
- }
- private static unsafe bool IsSortable(char *text, int length)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- int index = 0;
- UnicodeCategory uc;
- while (index < length)
- {
- if (char.IsHighSurrogate(text[index]))
- {
- if (index == length - 1 || !char.IsLowSurrogate(text[index+1]))
- return false; // unpaired surrogate
- uc = CharUnicodeInfo.GetUnicodeCategory(char.ConvertToUtf32(text[index], text[index+1]));
- if (uc == UnicodeCategory.PrivateUse || uc == UnicodeCategory.OtherNotAssigned)
- return false;
- index += 2;
- continue;
- }
- if (char.IsLowSurrogate(text[index]))
- {
- return false; // unpaired surrogate
- }
- uc = CharUnicodeInfo.GetUnicodeCategory(text[index]);
- if (uc == UnicodeCategory.PrivateUse || uc == UnicodeCategory.OtherNotAssigned)
- {
- return false;
- }
- index++;
- }
- return true;
- }
- // -----------------------------
- // ---- PAL layer ends here ----
- // -----------------------------
- internal unsafe int GetHashCodeOfStringCore(ReadOnlySpan<char> source, CompareOptions options)
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
- if (source.Length == 0)
- {
- return 0;
- }
- // according to ICU User Guide the performance of ucol_getSortKey is worse when it is called with null output buffer
- // the solution is to try to fill the sort key in a temporary buffer of size equal 4 x string length
- // 1MB is the biggest array that can be rented from ArrayPool.Shared without memory allocation
- int sortKeyLength = (source.Length > 1024 * 1024 / 4) ? 0 : 4 * source.Length;
- byte[]? borrowedArray = null;
- Span<byte> sortKey = sortKeyLength <= 1024
- ? stackalloc byte[1024]
- : (borrowedArray = ArrayPool<byte>.Shared.Rent(sortKeyLength));
- fixed (char* pSource = &MemoryMarshal.GetReference(source))
- {
- fixed (byte* pSortKey = &MemoryMarshal.GetReference(sortKey))
- {
- sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, pSortKey, sortKey.Length, options);
- }
- if (sortKeyLength > sortKey.Length) // slow path for big strings
- {
- if (borrowedArray != null)
- {
- ArrayPool<byte>.Shared.Return(borrowedArray);
- }
- sortKey = (borrowedArray = ArrayPool<byte>.Shared.Rent(sortKeyLength));
- fixed (byte* pSortKey = &MemoryMarshal.GetReference(sortKey))
- {
- sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, pSource, source.Length, pSortKey, sortKey.Length, options);
- }
- }
- }
- if (sortKeyLength == 0 || sortKeyLength > sortKey.Length) // internal error (0) or a bug (2nd call failed) in ucol_getSortKey
- {
- throw new ArgumentException(SR.Arg_ExternalException);
- }
- int hash = Marvin.ComputeHash32(sortKey.Slice(0, sortKeyLength), Marvin.DefaultSeed);
- if (borrowedArray != null)
- {
- ArrayPool<byte>.Shared.Return(borrowedArray);
- }
- return hash;
- }
- private static CompareOptions GetOrdinalCompareOptions(CompareOptions options)
- {
- if ((options & CompareOptions.IgnoreCase) == CompareOptions.IgnoreCase)
- {
- return CompareOptions.OrdinalIgnoreCase;
- }
- else
- {
- return CompareOptions.Ordinal;
- }
- }
- private static bool CanUseAsciiOrdinalForOptions(CompareOptions options)
- {
- // Unlike the other Ignore options, IgnoreSymbols impacts ASCII characters (e.g. ').
- return (options & CompareOptions.IgnoreSymbols) == 0;
- }
- private SortVersion GetSortVersion()
- {
- Debug.Assert(!GlobalizationMode.Invariant);
- int sortVersion = Interop.Globalization.GetSortVersion(_sortHandle);
- return new SortVersion(sortVersion, LCID, new Guid(sortVersion, 0, 0, 0, 0, 0, 0,
- (byte) (LCID >> 24),
- (byte) ((LCID & 0x00FF0000) >> 16),
- (byte) ((LCID & 0x0000FF00) >> 8),
- (byte) (LCID & 0xFF)));
- }
- private static class SortHandleCache
- {
- // in most scenarios there is a limited number of cultures with limited number of sort options
- // so caching the sort handles and not freeing them is OK, see https://github.com/dotnet/coreclr/pull/25117 for more
- private static readonly Dictionary<string, IntPtr> s_sortNameToSortHandleCache = new Dictionary<string, IntPtr>();
- internal static IntPtr GetCachedSortHandle(string sortName)
- {
- lock (s_sortNameToSortHandleCache)
- {
- if (!s_sortNameToSortHandleCache.TryGetValue(sortName, out IntPtr result))
- {
- Interop.Globalization.ResultCode resultCode = Interop.Globalization.GetSortHandle(sortName, out result);
- if (resultCode == Interop.Globalization.ResultCode.OutOfMemory)
- throw new OutOfMemoryException();
- else if (resultCode != Interop.Globalization.ResultCode.Success)
- throw new ExternalException(SR.Arg_ExternalException);
- try
- {
- s_sortNameToSortHandleCache.Add(sortName, result);
- }
- catch
- {
- Interop.Globalization.CloseSortHandle(result);
- throw;
- }
- }
- return result;
- }
- }
- }
- // See https://github.com/dotnet/coreclr/blob/master/src/utilcode/util_nodependencies.cpp#L970
- private static readonly bool[] s_highCharTable = new bool[0x80]
- {
- true, /* 0x0, 0x0 */
- true, /* 0x1, .*/
- true, /* 0x2, .*/
- true, /* 0x3, .*/
- true, /* 0x4, .*/
- true, /* 0x5, .*/
- true, /* 0x6, .*/
- true, /* 0x7, .*/
- true, /* 0x8, .*/
- false, /* 0x9, */
- true, /* 0xA, */
- false, /* 0xB, .*/
- false, /* 0xC, .*/
- true, /* 0xD, */
- true, /* 0xE, .*/
- true, /* 0xF, .*/
- true, /* 0x10, .*/
- true, /* 0x11, .*/
- true, /* 0x12, .*/
- true, /* 0x13, .*/
- true, /* 0x14, .*/
- true, /* 0x15, .*/
- true, /* 0x16, .*/
- true, /* 0x17, .*/
- true, /* 0x18, .*/
- true, /* 0x19, .*/
- true, /* 0x1A, */
- true, /* 0x1B, .*/
- true, /* 0x1C, .*/
- true, /* 0x1D, .*/
- true, /* 0x1E, .*/
- true, /* 0x1F, .*/
- false, /*0x20, */
- false, /*0x21, !*/
- false, /*0x22, "*/
- false, /*0x23, #*/
- false, /*0x24, $*/
- false, /*0x25, %*/
- false, /*0x26, &*/
- true, /*0x27, '*/
- false, /*0x28, (*/
- false, /*0x29, )*/
- false, /*0x2A **/
- false, /*0x2B, +*/
- false, /*0x2C, ,*/
- true, /*0x2D, -*/
- false, /*0x2E, .*/
- false, /*0x2F, /*/
- false, /*0x30, 0*/
- false, /*0x31, 1*/
- false, /*0x32, 2*/
- false, /*0x33, 3*/
- false, /*0x34, 4*/
- false, /*0x35, 5*/
- false, /*0x36, 6*/
- false, /*0x37, 7*/
- false, /*0x38, 8*/
- false, /*0x39, 9*/
- false, /*0x3A, :*/
- false, /*0x3B, ;*/
- false, /*0x3C, <*/
- false, /*0x3D, =*/
- false, /*0x3E, >*/
- false, /*0x3F, ?*/
- false, /*0x40, @*/
- false, /*0x41, A*/
- false, /*0x42, B*/
- false, /*0x43, C*/
- false, /*0x44, D*/
- false, /*0x45, E*/
- false, /*0x46, F*/
- false, /*0x47, G*/
- false, /*0x48, H*/
- false, /*0x49, I*/
- false, /*0x4A, J*/
- false, /*0x4B, K*/
- false, /*0x4C, L*/
- false, /*0x4D, M*/
- false, /*0x4E, N*/
- false, /*0x4F, O*/
- false, /*0x50, P*/
- false, /*0x51, Q*/
- false, /*0x52, R*/
- false, /*0x53, S*/
- false, /*0x54, T*/
- false, /*0x55, U*/
- false, /*0x56, V*/
- false, /*0x57, W*/
- false, /*0x58, X*/
- false, /*0x59, Y*/
- false, /*0x5A, Z*/
- false, /*0x5B, [*/
- false, /*0x5C, \*/
- false, /*0x5D, ]*/
- false, /*0x5E, ^*/
- false, /*0x5F, _*/
- false, /*0x60, `*/
- false, /*0x61, a*/
- false, /*0x62, b*/
- false, /*0x63, c*/
- false, /*0x64, d*/
- false, /*0x65, e*/
- false, /*0x66, f*/
- false, /*0x67, g*/
- false, /*0x68, h*/
- false, /*0x69, i*/
- false, /*0x6A, j*/
- false, /*0x6B, k*/
- false, /*0x6C, l*/
- false, /*0x6D, m*/
- false, /*0x6E, n*/
- false, /*0x6F, o*/
- false, /*0x70, p*/
- false, /*0x71, q*/
- false, /*0x72, r*/
- false, /*0x73, s*/
- false, /*0x74, t*/
- false, /*0x75, u*/
- false, /*0x76, v*/
- false, /*0x77, w*/
- false, /*0x78, x*/
- false, /*0x79, y*/
- false, /*0x7A, z*/
- false, /*0x7B, {*/
- false, /*0x7C, |*/
- false, /*0x7D, }*/
- false, /*0x7E, ~*/
- true, /*0x7F, */
- };
- }
- }
|