File: src\libraries\System.Private.CoreLib\src\System\Globalization\InvariantModeCasing.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
 
namespace System.Globalization
{
    internal static class InvariantModeCasing
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static char ToLower(char c) => CharUnicodeInfo.ToLower(c);
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static char ToUpper(char c) => CharUnicodeInfo.ToUpper(c);
 
        internal static string ToLower(string s)
        {
            if (s.Length == 0)
            {
                return string.Empty;
            }
 
            ReadOnlySpan<char> source = s;
 
            int i = 0;
            while (i < s.Length)
            {
                if (char.IsHighSurrogate(source[i]) && i < s.Length - 1 && char.IsLowSurrogate(source[i + 1]))
                {
                    SurrogateCasing.ToLower(source[i], source[i + 1], out char h, out char l);
                    if (source[i] != h || source[i + 1] != l)
                    {
                        break;
                    }
 
                    i += 2;
                    continue;
                }
 
                if (ToLower(source[i]) != source[i])
                {
                    break;
                }
 
                i++;
            }
 
            if (i >= s.Length)
            {
                return s;
            }
 
            string result = string.FastAllocateString(s.Length);
            var destination = new Span<char>(ref result.GetRawStringData(), result.Length);
            ReadOnlySpan<char> src = s;
            src.Slice(0, i).CopyTo(destination);
            ToLower(src.Slice(i), destination.Slice(i));
 
            return result;
        }
 
        internal static string ToUpper(string s)
        {
            if (s.Length == 0)
            {
                return string.Empty;
            }
 
            ReadOnlySpan<char> source = s;
 
            int i = 0;
            while (i < s.Length)
            {
                if (char.IsHighSurrogate(source[i]) && i < s.Length - 1 && char.IsLowSurrogate(source[i + 1]))
                {
                    SurrogateCasing.ToUpper(source[i], source[i + 1], out char h, out char l);
                    if (source[i] != h || source[i + 1] != l)
                    {
                        break;
                    }
 
                    i += 2;
                    continue;
                }
 
                if (ToUpper(source[i]) != source[i])
                {
                    break;
                }
 
                i++;
            }
 
            if (i >= s.Length)
            {
                return s;
            }
 
            string result = string.FastAllocateString(s.Length);
            var destination = new Span<char>(ref result.GetRawStringData(), result.Length);
            ReadOnlySpan<char> src = s;
            src.Slice(0, i).CopyTo(destination);
            ToUpper(src.Slice(i), destination.Slice(i));
 
            return result;
        }
 
        internal static void ToUpper(ReadOnlySpan<char> source, Span<char> destination)
        {
            Debug.Assert(GlobalizationMode.Invariant);
            Debug.Assert(source.Length <= destination.Length);
 
            for (int i = 0; i < source.Length; i++)
            {
                char c = source[i];
                if (char.IsHighSurrogate(c) && i < source.Length - 1)
                {
                    char cl = source[i + 1];
                    if (char.IsLowSurrogate(cl))
                    {
                        // well formed surrogates
                        SurrogateCasing.ToUpper(c, cl, out char h, out char l);
                        destination[i] = h;
                        destination[i + 1] = l;
                        i++; // skip the low surrogate
                        continue;
                    }
                }
 
                destination[i] = ToUpper(c);
            }
        }
 
        internal static void ToLower(ReadOnlySpan<char> source, Span<char> destination)
        {
            Debug.Assert(GlobalizationMode.Invariant);
            Debug.Assert(source.Length <= destination.Length);
 
            for (int i = 0; i < source.Length; i++)
            {
                char c = source[i];
                if (char.IsHighSurrogate(c) && i < source.Length - 1)
                {
                    char cl = source[i + 1];
                    if (char.IsLowSurrogate(cl))
                    {
                        // well formed surrogates
                        SurrogateCasing.ToLower(c, cl, out char h, out char l);
                        destination[i] = h;
                        destination[i + 1] = l;
                        i++; // skip the low surrogate
                        continue;
                    }
                }
 
                destination[i] = ToLower(c);
            }
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static (uint, int) GetScalar(ref char source, int index, int length)
        {
            char charA = source;
            if (!char.IsHighSurrogate(charA) || index >= length - 1)
            {
                return ((uint)charA, 1);
            }
 
            char charB = Unsafe.Add(ref source, 1);
            if (!char.IsLowSurrogate(charB))
            {
                return ((uint)charA, 1);
            }
 
            return (UnicodeUtility.GetScalarFromUtf16SurrogatePair(charA, charB), 2);
        }
 
        internal static int CompareStringIgnoreCase(ref char strA, int lengthA, ref char strB, int lengthB)
        {
            Debug.Assert(GlobalizationMode.Invariant);
 
            int length = Math.Min(lengthA, lengthB);
 
            ref char charA = ref strA;
            ref char charB = ref strB;
 
            int index = 0;
 
            while (index < length)
            {
                (uint codePointA, int codePointLengthA) = GetScalar(ref charA, index, lengthA);
                (uint codePointB, int codePointLengthB) = GetScalar(ref charB, index, lengthB);
 
                if (codePointA == codePointB)
                {
                    Debug.Assert(codePointLengthA == codePointLengthB);
                    index += codePointLengthA;
                    charA = ref Unsafe.Add(ref charA, codePointLengthA);
                    charB = ref Unsafe.Add(ref charB, codePointLengthB);
                    continue;
                }
 
                uint aUpper = CharUnicodeInfo.ToUpper(codePointA);
                uint bUpper = CharUnicodeInfo.ToUpper(codePointB);
 
                if (aUpper == bUpper)
                {
                    Debug.Assert(codePointLengthA == codePointLengthB);
                    index += codePointLengthA;
                    charA = ref Unsafe.Add(ref charA, codePointLengthA);
                    charB = ref Unsafe.Add(ref charB, codePointLengthB);
                    continue;
                }
 
                return (int)aUpper - (int)bUpper;
            }
 
            return lengthA - lengthB;
        }
 
        internal static unsafe int IndexOfIgnoreCase(ReadOnlySpan<char> source, ReadOnlySpan<char> value)
        {
            Debug.Assert(value.Length > 0);
            Debug.Assert(value.Length <= source.Length);
            Debug.Assert(GlobalizationMode.Invariant);
 
            fixed (char* pSource = &MemoryMarshal.GetReference(source))
            fixed (char* pValue  = &MemoryMarshal.GetReference(value))
            {
                char* pSourceLimit = pSource + (source.Length - value.Length);
                char* pValueLimit = pValue + value.Length - 1;
                char* pCurrentSource = pSource;
 
                while (pCurrentSource <= pSourceLimit)
                {
                    char *pVal = pValue;
                    char *pSrc = pCurrentSource;
 
                    while (pVal <= pValueLimit)
                    {
                        if (!char.IsHighSurrogate(*pVal) || pVal == pValueLimit)
                        {
                            if (*pVal != *pSrc && ToUpper(*pVal) != ToUpper(*pSrc))
                                break; // no match
 
                            pVal++;
                            pSrc++;
                            continue;
                        }
 
                        if (char.IsHighSurrogate(*pSrc) && char.IsLowSurrogate(*(pSrc + 1)) && char.IsLowSurrogate(*(pVal + 1)))
                        {
                            // Well formed surrogates
                            // both the source and the Value have well-formed surrogates.
                            if (!SurrogateCasing.Equal(*pSrc, *(pSrc + 1), *pVal, *(pVal + 1)))
                                break; // no match
 
                            pSrc += 2;
                            pVal += 2;
                            continue;
                        }
 
                        if (*pVal != *pSrc)
                            break; // no match
 
                        pSrc++;
                        pVal++;
                    }
 
                    if (pVal > pValueLimit)
                    {
                        // Found match.
                        return (int)(pCurrentSource - pSource);
                    }
 
                    pCurrentSource++;
                }
 
                return -1;
            }
        }
 
        internal static unsafe int LastIndexOfIgnoreCase(ReadOnlySpan<char> source, ReadOnlySpan<char> value)
        {
            Debug.Assert(value.Length > 0);
            Debug.Assert(value.Length <= source.Length);
            Debug.Assert(GlobalizationMode.Invariant);
 
            fixed (char* pSource = &MemoryMarshal.GetReference(source))
            fixed (char* pValue  = &MemoryMarshal.GetReference(value))
            {
                char* pValueLimit = pValue + value.Length - 1;
                char* pCurrentSource = pSource + (source.Length - value.Length);
 
                while (pCurrentSource >= pSource)
                {
                    char *pVal = pValue;
                    char *pSrc = pCurrentSource;
 
                    while (pVal <= pValueLimit)
                    {
                        if (!char.IsHighSurrogate(*pVal) || pVal == pValueLimit)
                        {
                            if (*pVal != *pSrc && ToUpper(*pVal) != ToUpper(*pSrc))
                                break; // no match
 
                            pVal++;
                            pSrc++;
                            continue;
                        }
 
                        if (char.IsHighSurrogate(*pSrc) && char.IsLowSurrogate(*(pSrc + 1)) && char.IsLowSurrogate(*(pVal + 1)))
                        {
                            // Well formed surrogates
                            // both the source and the Value have well-formed surrogates.
                            if (!SurrogateCasing.Equal(*pSrc, *(pSrc + 1), *pVal, *(pVal + 1)))
                                break; // no match
 
                            pSrc += 2;
                            pVal += 2;
                            continue;
                        }
 
                        if (*pVal != *pSrc)
                            break; // no match
 
                        pSrc++;
                        pVal++;
                    }
 
                    if (pVal > pValueLimit)
                    {
                        // Found match.
                        return (int)(pCurrentSource - pSource);
                    }
 
                    pCurrentSource--;
                }
 
                return -1;
            }
        }
    }
}