Add ReadOnlySpan string-like ToLower/ToUpper API with globalization support (#16379)
authorAhson Khan <ahkha@microsoft.com>
Fri, 16 Feb 2018 02:43:43 +0000 (18:43 -0800)
committerGitHub <noreply@github.com>
Fri, 16 Feb 2018 02:43:43 +0000 (18:43 -0800)
* Add ReadOnlySpan string-like ToLower/ToUpper API with globalization support

* Address PR feedback.

src/mscorlib/shared/System/Globalization/TextInfo.Unix.cs
src/mscorlib/shared/System/Globalization/TextInfo.Windows.cs
src/mscorlib/shared/System/Globalization/TextInfo.cs
src/mscorlib/shared/System/Span.NonGeneric.cs
src/mscorlib/src/System/Globalization/CompareInfo.Unix.cs
src/mscorlib/src/System/ThrowHelper.cs

index 33bd61f..5186394 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Diagnostics;
+using System.Runtime.InteropServices;
 using System.Security;
 using System.Text;
 
@@ -64,6 +65,52 @@ namespace System.Globalization
             return result;
         }
 
+        internal unsafe void ChangeCase(ReadOnlySpan<char> source, Span<char> destination, bool toUpper)
+        {
+            Debug.Assert(!_invariantMode);
+            Debug.Assert(destination.Length >= source.Length);
+
+            if (source.IsEmpty)
+            {
+                return;
+            }
+            
+            fixed (char* pSource = &MemoryMarshal.GetReference(source))
+            {
+                fixed (char* pResult = &MemoryMarshal.GetReference(destination))
+                {
+                    if (IsAsciiCasingSameAsInvariant)
+                    {
+                        int length = source.Length;
+                        char* a = pSource, b = pResult;
+                        if (toUpper)
+                        {
+                            while (length-- != 0 && *a < 0x80)
+                            {
+                                *b++ = ToUpperAsciiInvariant(*a++);
+                            }
+                        }
+                        else
+                        {
+                            while (length-- != 0 && *a < 0x80)
+                            {
+                                *b++ = ToLowerAsciiInvariant(*a++);
+                            }
+                        }
+
+                        if (length != 0)
+                        {
+                            ChangeCase(a, source.Length - length, b, destination.Length - length, toUpper);
+                        }
+                    }
+                    else
+                    {
+                        ChangeCase(pSource, source.Length, pResult, destination.Length, toUpper);
+                    }
+                }
+            }
+        }
+
         private unsafe char ChangeCase(char c, bool toUpper)
         {
             Debug.Assert(!_invariantMode);
index 8b27e42..015b37f 100644 (file)
@@ -3,6 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Diagnostics;
+using System.Runtime.InteropServices;
 
 namespace System.Globalization
 {
@@ -75,6 +76,43 @@ namespace System.Globalization
             return result;
         }
 
+        internal unsafe void ChangeCase(ReadOnlySpan<char> source, Span<char> destination, bool toUpper)
+        {
+            Debug.Assert(!_invariantMode);
+            Debug.Assert(destination.Length >= source.Length);
+
+            if (source.IsEmpty)
+            {
+                return;
+            }
+
+            int ret;
+
+            // Check for Invariant to avoid A/V in LCMapStringEx
+            uint linguisticCasing = IsInvariantLocale(_textInfoName) ? 0 : LCMAP_LINGUISTIC_CASING;
+
+            fixed (char* pSource = &MemoryMarshal.GetReference(source))
+            fixed (char* pResult = &MemoryMarshal.GetReference(destination))
+            {
+                ret = Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _textInfoName,
+                                                    linguisticCasing | (toUpper ? LCMAP_UPPERCASE : LCMAP_LOWERCASE),
+                                                    pSource,
+                                                    source.Length,
+                                                    pResult,
+                                                    source.Length,
+                                                    null,
+                                                    null,
+                                                    _sortHandle);
+            }
+
+            if (ret == 0)
+            {
+                throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
+            }
+
+            Debug.Assert(ret == source.Length, "Expected getting the same length of the original span");
+        }
+
         private unsafe char ChangeCase(char c, bool toUpper)
         {
             Debug.Assert(!_invariantMode);
index d9d6f3b..631c8c0 100644 (file)
@@ -258,6 +258,16 @@ namespace System.Globalization
             }
         }
 
+        internal void ToLowerAsciiInvariant(ReadOnlySpan<char> source, Span<char> destination)
+        {
+            Debug.Assert(destination.Length >= source.Length);
+
+            for (int i = 0; i < source.Length; i++)
+            {
+                destination[i] = ToLowerAsciiInvariant(source[i]);
+            }
+        }
+
         private unsafe string ToUpperAsciiInvariant(string s)
         {
             if (s.Length == 0)
@@ -304,6 +314,16 @@ namespace System.Globalization
             }
         }
 
+        internal void ToUpperAsciiInvariant(ReadOnlySpan<char> source, Span<char> destination)
+        {
+            Debug.Assert(destination.Length >= source.Length);
+
+            for (int i = 0; i < source.Length; i++)
+            {
+                destination[i] = ToUpperAsciiInvariant(source[i]);
+            }
+        }
+
         private static char ToLowerAsciiInvariant(char c)
         {
             if ((uint)(c - 'A') <= (uint)('Z' - 'A'))
index 996f73a..4ef1c23 100644 (file)
@@ -23,8 +23,77 @@ namespace System
     /// </summary>
     public static class Span
     {
-        // s_invariantMode is defined for the perf reason as accessing the instance field is faster than access the static property GlobalizationMode.Invariant
-        private static readonly bool s_invariantMode = GlobalizationMode.Invariant;
+        /// <summary>
+        /// Copies the characters from the source span into the destination, converting each character to lowercase.
+        /// </summary>
+        public static int ToLower(this ReadOnlySpan<char> source, Span<char> destination, CultureInfo culture)
+        {
+            if (culture == null)
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.culture);
+
+            // Assuming that changing case does not affect length
+            if (destination.Length < source.Length)
+                return -1;
+
+            if (GlobalizationMode.Invariant)
+                culture.TextInfo.ToLowerAsciiInvariant(source, destination);
+            else
+                culture.TextInfo.ChangeCase(source, destination, toUpper: false);
+            return source.Length;
+        }
+
+        /// <summary>
+        /// Copies the characters from the source span into the destination, converting each character to lowercase
+        /// using the casing rules of the invariant culture.
+        /// </summary>
+        public static int ToLowerInvariant(this ReadOnlySpan<char> source, Span<char> destination)
+        {
+            // Assuming that changing case does not affect length
+            if (destination.Length < source.Length)
+                return -1;
+
+            if (GlobalizationMode.Invariant)
+                CultureInfo.InvariantCulture.TextInfo.ToLowerAsciiInvariant(source, destination);
+            else
+                CultureInfo.InvariantCulture.TextInfo.ChangeCase(source, destination, toUpper: false);
+            return source.Length;
+        }
+
+        /// <summary>
+        /// Copies the characters from the source span into the destination, converting each character to uppercase.
+        /// </summary>
+        public static int ToUpper(this ReadOnlySpan<char> source, Span<char> destination, CultureInfo culture)
+        {
+            if (culture == null)
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.culture);
+
+            // Assuming that changing case does not affect length
+            if (destination.Length < source.Length)
+                return -1;
+
+            if (GlobalizationMode.Invariant)
+                culture.TextInfo.ToUpperAsciiInvariant(source, destination);
+            else
+                culture.TextInfo.ChangeCase(source, destination, toUpper: true);
+            return source.Length;
+        }
+
+        /// <summary>
+        /// Copies the characters from the source span into the destination, converting each character to uppercase
+        /// using the casing rules of the invariant culture.
+        /// </summary>
+        public static int ToUpperInvariant(this ReadOnlySpan<char> source, Span<char> destination)
+        {
+            // Assuming that changing case does not affect length
+            if (destination.Length < source.Length)
+                return -1;
+
+            if (GlobalizationMode.Invariant)
+                CultureInfo.InvariantCulture.TextInfo.ToUpperAsciiInvariant(source, destination);
+            else
+                CultureInfo.InvariantCulture.TextInfo.ChangeCase(source, destination, toUpper: true);
+            return source.Length;
+        }
 
         /// <summary>
         /// Determines whether the beginning of the span matches the specified value when compared using the specified comparison option.
@@ -66,7 +135,7 @@ namespace System
         {
             Debug.Assert(value.Length != 0);
 
-            if (s_invariantMode)
+            if (GlobalizationMode.Invariant)
             {
                 return StartsWithOrdinalHelper(span, value);
             }
@@ -83,7 +152,7 @@ namespace System
         {
             Debug.Assert(value.Length != 0);
 
-            if (s_invariantMode)
+            if (GlobalizationMode.Invariant)
             {
                 return StartsWithOrdinalIgnoreCaseHelper(span, value);
             }
@@ -220,7 +289,7 @@ namespace System
         {
             Debug.Assert(value.Length != 0);
 
-            if (s_invariantMode)
+            if (GlobalizationMode.Invariant)
             {
                 return EndsWithOrdinalHelper(span, value);
             }
@@ -237,7 +306,7 @@ namespace System
         {
             Debug.Assert(value.Length != 0);
 
-            if (s_invariantMode)
+            if (GlobalizationMode.Invariant)
             {
                 return EndsWithOrdinalIgnoreCaseHelper(span, value);
             }
index d601c3e..0b7df3c 100644 (file)
@@ -380,7 +380,7 @@ namespace System.Globalization
                 char* a = ap;
                 char* b = bp;
 
-                while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!HighCharTable[*a]) && (!HighCharTable[*b]))
+                while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
                 {
                     int charA = *a;
                     int charB = *b;
@@ -427,7 +427,7 @@ namespace System.Globalization
                 char* a = ap;
                 char* b = bp;
 
-                while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!HighCharTable[*a]) && (!HighCharTable[*b]))
+                while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
                 {
                     int charA = *a;
                     int charB = *b;
@@ -605,7 +605,7 @@ namespace System.Globalization
         }
 
         // See https://github.com/dotnet/coreclr/blob/master/src/utilcode/util_nodependencies.cpp#L970
-        private static readonly bool[] HighCharTable = new bool[0x80]
+        private static readonly bool[] s_highCharTable = new bool[0x80]
         {
             true, /* 0x0, 0x0 */
             true, /* 0x1, .*/
index fd79c5a..5daac2a 100644 (file)
@@ -470,7 +470,8 @@ namespace System
         ownedMemory,
         pointer,
         start,
-        format
+        format,
+        culture
     }
 
     //