diff options
Diffstat (limited to 'src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs')
-rw-r--r-- | src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs | 275 |
1 files changed, 176 insertions, 99 deletions
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs index c369c816b..92742c7b9 100644 --- a/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs +++ b/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.cs @@ -16,6 +16,8 @@ using System.Reflection; using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.Serialization; +using System.Buffers; +using Internal.Runtime.CompilerServices; namespace System.Globalization { @@ -105,7 +107,7 @@ namespace System.Globalization { throw new ArgumentNullException(nameof(assembly)); } - if (assembly != typeof(Object).Module.Assembly) + if (assembly != typeof(object).Module.Assembly) { throw new ArgumentException(SR.Argument_OnlyMscorlib); } @@ -132,7 +134,7 @@ namespace System.Globalization throw new ArgumentNullException(name == null ? nameof(name) : nameof(assembly)); } - if (assembly != typeof(Object).Module.Assembly) + if (assembly != typeof(object).Module.Assembly) { throw new ArgumentException(SR.Argument_OnlyMscorlib); } @@ -302,7 +304,7 @@ namespace System.Globalization { if (options == CompareOptions.OrdinalIgnoreCase) { - return String.Compare(string1, string2, StringComparison.OrdinalIgnoreCase); + return string.Compare(string1, string2, StringComparison.OrdinalIgnoreCase); } // Verify the options before we do any real comparison. @@ -313,7 +315,7 @@ namespace System.Globalization throw new ArgumentException(SR.Argument_CompareOptionOrdinal, nameof(options)); } - return String.CompareOrdinal(string1, string2); + return string.CompareOrdinal(string1, string2); } if ((options & ValidCompareMaskOffFlags) != 0) @@ -339,9 +341,9 @@ namespace System.Globalization if (_invariantMode) { if ((options & CompareOptions.IgnoreCase) != 0) - return CompareOrdinalIgnoreCase(string1, 0, string1.Length, string2, 0, string2.Length); + return CompareOrdinalIgnoreCase(string1, string2); - return String.CompareOrdinal(string1, string2); + return string.CompareOrdinal(string1, string2); } return CompareString(string1.AsSpan(), string2.AsSpan(), options); @@ -389,38 +391,26 @@ namespace System.Globalization return CompareString(string1, string2, options); } - // TODO https://github.com/dotnet/corefx/issues/21395: Expose this publicly? - internal virtual int Compare(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2, CompareOptions options) + internal int CompareOptionNone(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2) { - if (options == CompareOptions.OrdinalIgnoreCase) - { - return CompareOrdinalIgnoreCase(string1, string2); - } + // Check for empty span or span from a null string + if (string1.Length == 0 || string2.Length == 0) + return string1.Length - string2.Length; - // Verify the options before we do any real comparison. - if ((options & CompareOptions.Ordinal) != 0) - { - if (options != CompareOptions.Ordinal) - { - throw new ArgumentException(SR.Argument_CompareOptionOrdinal, nameof(options)); - } - - return string.CompareOrdinal(string1, string2); - } - - if ((options & ValidCompareMaskOffFlags) != 0) - { - throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options)); - } + return _invariantMode ? + string.CompareOrdinal(string1, string2) : + CompareString(string1, string2, CompareOptions.None); + } - if (_invariantMode) - { - return (options & CompareOptions.IgnoreCase) != 0 ? - CompareOrdinalIgnoreCase(string1, string2) : - string.CompareOrdinal(string1, string2); - } + internal int CompareOptionIgnoreCase(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2) + { + // Check for empty span or span from a null string + if (string1.Length == 0 || string2.Length == 0) + return string1.Length - string2.Length; - return CompareString(string1, string2, options); + return _invariantMode ? + CompareOrdinalIgnoreCase(string1, string2) : + CompareString(string1, string2, CompareOptions.IgnoreCase); } //////////////////////////////////////////////////////////////////////// @@ -459,7 +449,7 @@ namespace System.Globalization { if (options == CompareOptions.OrdinalIgnoreCase) { - int result = String.Compare(string1, offset1, string2, offset2, length1 < length2 ? length1 : length2, StringComparison.OrdinalIgnoreCase); + int result = string.Compare(string1, offset1, string2, offset2, length1 < length2 ? length1 : length2, StringComparison.OrdinalIgnoreCase); if ((length1 != length2) && result == 0) return (length1 > length2 ? 1 : -1); return (result); @@ -511,35 +501,23 @@ namespace System.Globalization return (1); } + ReadOnlySpan<char> span1 = string1.AsSpan(offset1, length1); + ReadOnlySpan<char> span2 = string2.AsSpan(offset2, length2); + if (options == CompareOptions.Ordinal) { - return CompareOrdinal(string1, offset1, length1, - string2, offset2, length2); + return string.CompareOrdinal(span1, span2); } if (_invariantMode) { if ((options & CompareOptions.IgnoreCase) != 0) - return CompareOrdinalIgnoreCase(string1, offset1, length1, string2, offset2, length2); + return CompareOrdinalIgnoreCase(span1, span2); - return CompareOrdinal(string1, offset1, length1, string2, offset2, length2); + return string.CompareOrdinal(span1, span2); } - return CompareString( - string1.AsSpan().Slice(offset1, length1), - string2.AsSpan().Slice(offset2, length2), - options); - } - - private static int CompareOrdinal(string string1, int offset1, int length1, string string2, int offset2, int length2) - { - int result = String.CompareOrdinal(string1, offset1, string2, offset2, - (length1 < length2 ? length1 : length2)); - if ((length1 != length2) && result == 0) - { - return (length1 > length2 ? 1 : -1); - } - return (result); + return CompareString(span1, span2, options); } // @@ -551,57 +529,103 @@ namespace System.Globalization { Debug.Assert(indexA + lengthA <= strA.Length); Debug.Assert(indexB + lengthB <= strB.Length); - return CompareOrdinalIgnoreCase(strA.AsSpan().Slice(indexA, lengthA), strB.AsSpan().Slice(indexB, lengthB)); + return CompareOrdinalIgnoreCase( + ref Unsafe.Add(ref strA.GetRawStringData(), indexA), + lengthA, + ref Unsafe.Add(ref strB.GetRawStringData(), indexB), + lengthB); } - internal static unsafe int CompareOrdinalIgnoreCase(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB) + internal static int CompareOrdinalIgnoreCase(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB) { - int length = Math.Min(strA.Length, strB.Length); + return CompareOrdinalIgnoreCase(ref MemoryMarshal.GetReference(strA), strA.Length, ref MemoryMarshal.GetReference(strB), strB.Length); + } + + internal static int CompareOrdinalIgnoreCase(string strA, string strB) + { + return CompareOrdinalIgnoreCase(ref strA.GetRawStringData(), strA.Length, ref strB.GetRawStringData(), strB.Length); + } + + internal static int CompareOrdinalIgnoreCase(ref char strA, int lengthA, ref char strB, int lengthB) + { + int length = Math.Min(lengthA, lengthB); int range = length; - fixed (char* ap = &MemoryMarshal.GetReference(strA)) - fixed (char* bp = &MemoryMarshal.GetReference(strB)) - { - char* a = ap; - char* b = bp; + ref char charA = ref strA; + ref char charB = ref strB; - // in InvariantMode we support all range and not only the ascii characters. - char maxChar = (char) (GlobalizationMode.Invariant ? 0xFFFF : 0x7F); + // in InvariantMode we support all range and not only the ascii characters. + char maxChar = (GlobalizationMode.Invariant ? (char)0xFFFF : (char)0x7F); - while (length != 0 && (*a <= maxChar) && (*b <= maxChar)) + while (length != 0 && charA <= maxChar && charB <= maxChar) + { + // Ordinal equals or lowercase equals if the result ends up in the a-z range + if (charA == charB || + ((charA | 0x20) == (charB | 0x20) && + (uint)((charA | 0x20) - 'a') <= (uint)('z' - 'a'))) { - int charA = *a; - int charB = *b; - - if (charA == charB) - { - a++; b++; - length--; - continue; - } + length--; + charA = ref Unsafe.Add(ref charA, 1); + charB = ref Unsafe.Add(ref charB, 1); + } + else + { + int currentA = charA; + int currentB = charB; - // uppercase both chars - notice that we need just one compare per char - if ((uint)(charA - 'a') <= 'z' - 'a') charA -= 0x20; - if ((uint)(charB - 'a') <= 'z' - 'a') charB -= 0x20; + // Uppercase both chars if needed + if ((uint)(charA - 'a') <= 'z' - 'a') + currentA -= 0x20; + if ((uint)(charB - 'a') <= 'z' - 'a') + currentB -= 0x20; // Return the (case-insensitive) difference between them. - if (charA != charB) - return charA - charB; - - // Next char - a++; b++; - length--; + return currentA - currentB; } + } + + if (length == 0) + return lengthA - lengthB; + + Debug.Assert(!GlobalizationMode.Invariant); - if (length == 0) - return strA.Length - strB.Length; + range -= length; - Debug.Assert(!GlobalizationMode.Invariant); + return CompareStringOrdinalIgnoreCase(ref charA, lengthA - range, ref charB, lengthB - range); + } + + + internal static bool EqualsOrdinalIgnoreCase(ref char strA, ref char strB, int length) + { + ref char charA = ref strA; + ref char charB = ref strB; - range -= length; + // in InvariantMode we support all range and not only the ascii characters. + char maxChar = (GlobalizationMode.Invariant ? (char)0xFFFF : (char)0x7F); - return CompareStringOrdinalIgnoreCase(a, strA.Length - range, b, strB.Length - range); + while (length != 0 && charA <= maxChar && charB <= maxChar) + { + // Ordinal equals or lowercase equals if the result ends up in the a-z range + if (charA == charB || + ((charA | 0x20) == (charB | 0x20) && + (uint)((charA | 0x20) - 'a') <= (uint)('z' - 'a'))) + { + length--; + charA = ref Unsafe.Add(ref charA, 1); + charB = ref Unsafe.Add(ref charB, 1); + } + else + { + return false; + } } + + if (length == 0) + return true; + + Debug.Assert(!GlobalizationMode.Invariant); + + return CompareStringOrdinalIgnoreCase(ref charA, length, ref charB, length) == 0; } //////////////////////////////////////////////////////////////////////// @@ -609,7 +633,7 @@ namespace System.Globalization // IsPrefix // // Determines whether prefix is a prefix of string. If prefix equals - // String.Empty, true is returned. + // string.Empty, true is returned. // //////////////////////////////////////////////////////////////////////// public virtual bool IsPrefix(string source, string prefix, CompareOptions options) @@ -674,7 +698,7 @@ namespace System.Globalization // IsSuffix // // Determines whether suffix is a suffix of string. If suffix equals - // String.Empty, true is returned. + // string.Empty, true is returned. // //////////////////////////////////////////////////////////////////////// public virtual bool IsSuffix(string source, string suffix, CompareOptions options) @@ -741,7 +765,7 @@ namespace System.Globalization // // Returns the first index where value is found in string. The // search starts from startIndex and ends at endIndex. Returns -1 if - // the specified value is not found. If value equals String.Empty, + // the specified value is not found. If value equals string.Empty, // startIndex is returned. Throws IndexOutOfRange if startIndex or // endIndex is less than zero or greater than the length of string. // Throws ArgumentException if value is null. @@ -910,16 +934,36 @@ namespace System.Globalization return IndexOfCore(source, value, startIndex, count, options, null); } - internal virtual int IndexOfOrdinal(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase) + internal int IndexOfOrdinal(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase) + { + Debug.Assert(!_invariantMode); + Debug.Assert(!source.IsEmpty); + Debug.Assert(!value.IsEmpty); + return IndexOfOrdinalCore(source, value, ignoreCase, fromBeginning: true); + } + + internal int LastIndexOfOrdinal(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase) + { + Debug.Assert(!_invariantMode); + Debug.Assert(!source.IsEmpty); + Debug.Assert(!value.IsEmpty); + return IndexOfOrdinalCore(source, value, ignoreCase, fromBeginning: false); + } + + internal unsafe int IndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, CompareOptions options) { Debug.Assert(!_invariantMode); - return IndexOfOrdinalCore(source, value, ignoreCase); + Debug.Assert(!source.IsEmpty); + Debug.Assert(!value.IsEmpty); + return IndexOfCore(source, value, options, null, fromBeginning: true); } - internal unsafe virtual int IndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, CompareOptions options) + internal unsafe int LastIndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, CompareOptions options) { Debug.Assert(!_invariantMode); - return IndexOfCore(source, value, options, null); + Debug.Assert(!source.IsEmpty); + Debug.Assert(!value.IsEmpty); + return IndexOfCore(source, value, options, null, fromBeginning: false); } // The following IndexOf overload is mainly used by String.Replace. This overload assumes the parameters are already validated @@ -985,7 +1029,7 @@ namespace System.Globalization // // Returns the last index where value is found in string. The // search starts from startIndex and ends at endIndex. Returns -1 if - // the specified value is not found. If value equals String.Empty, + // the specified value is not found. If value equals string.Empty, // endIndex is returned. Throws IndexOutOfRange if startIndex or // endIndex is less than zero or greater than the length of string. // Throws ArgumentException if value is null. @@ -993,7 +1037,7 @@ namespace System.Globalization //////////////////////////////////////////////////////////////////////// - public virtual int LastIndexOf(String source, char value) + public virtual int LastIndexOf(string source, char value) { if (source == null) throw new ArgumentNullException(nameof(source)); @@ -1208,7 +1252,7 @@ namespace System.Globalization //////////////////////////////////////////////////////////////////////// - public override bool Equals(Object value) + public override bool Equals(object value) { CompareInfo that = value as CompareInfo; @@ -1237,6 +1281,34 @@ namespace System.Globalization return (this.Name.GetHashCode()); } + internal static unsafe int GetIgnoreCaseHash(string source) + { + Debug.Assert(source != null, "source must not be null"); + + // Do not allocate on the stack if string is empty + if (source.Length == 0) + { + return source.GetHashCode(); + } + + char[] borrowedArr = null; + Span<char> span = source.Length <= 255 ? + stackalloc char[255] : + (borrowedArr = ArrayPool<char>.Shared.Rent(source.Length)); + + int charsWritten = source.AsSpan().ToUpperInvariant(span); + + // Slice the array to the size returned by ToUpperInvariant. + int hash = Marvin.ComputeHash32(MemoryMarshal.AsBytes(span.Slice(0, charsWritten)), Marvin.DefaultSeed); + + // Return the borrowed array if necessary. + if (borrowedArr != null) + { + ArrayPool<char>.Shared.Return(borrowedArr); + } + + return hash; + } //////////////////////////////////////////////////////////////////////// // @@ -1277,6 +1349,11 @@ namespace System.Globalization throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options)); } + if (_invariantMode) + { + return ((options & CompareOptions.IgnoreCase) != 0) ? GetIgnoreCaseHash(source) : source.GetHashCode(); + } + return GetHashCodeOfStringCore(source, options); } @@ -1294,7 +1371,7 @@ namespace System.Globalization if (options == CompareOptions.OrdinalIgnoreCase) { - return TextInfo.GetHashCodeOrdinalIgnoreCase(source); + return GetIgnoreCaseHash(source); } // |