Rune
using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text.Unicode;
namespace System.Text
{
[DebuggerDisplay("{DebuggerDisplay,nq}")]
internal readonly struct Rune : IComparable, IComparable<System.Text.Rune>, IEquatable<System.Text.Rune>
{
internal const int MaxUtf16CharsPerRune = 2;
internal const int MaxUtf8BytesPerRune = 4;
private const char HighSurrogateStart = '�';
private const char LowSurrogateStart = '�';
private const int HighSurrogateRange = 1023;
private const byte IsWhiteSpaceFlag = 128;
private const byte IsLetterOrDigitFlag = 64;
private const byte UnicodeCategoryMask = 31;
private readonly uint _value;
private unsafe static ReadOnlySpan<byte> AsciiCharInfo => new ReadOnlySpan<byte>(&global::<PrivateImplementationDetails>.2F3EFC9595514E83DED03093C4F3E3C781A650E1AAB8CA350537CD1A47E1EE8E, 128);
private string DebuggerDisplay => FormattableString.Invariant(FormattableStringFactory.Create("U+{0:X4} '{1}'", _value, IsValid(_value) ? ToString() : "�"));
public bool IsAscii => System.Text.UnicodeUtility.IsAsciiCodePoint(_value);
public bool IsBmp => System.Text.UnicodeUtility.IsBmpCodePoint(_value);
public int Plane => System.Text.UnicodeUtility.GetPlane(_value);
public static System.Text.Rune ReplacementChar => UnsafeCreate(65533);
public int Utf16SequenceLength => System.Text.UnicodeUtility.GetUtf16SequenceLength(_value);
public int Utf8SequenceLength => System.Text.UnicodeUtility.GetUtf8SequenceLength(_value);
public int Value => (int)_value;
public Rune(char ch)
{
if (System.Text.UnicodeUtility.IsSurrogateCodePoint(ch))
System.ThrowHelper.ThrowArgumentOutOfRangeException(System.ExceptionArgument.ch);
_value = ch;
}
public Rune(char highSurrogate, char lowSurrogate)
{
this = new System.Text.Rune((uint)char.ConvertToUtf32(highSurrogate, lowSurrogate), false);
}
public Rune(int value)
{
this = new System.Text.Rune((uint)value);
}
[CLSCompliant(false)]
public Rune(uint value)
{
if (!System.Text.UnicodeUtility.IsValidUnicodeScalar(value))
System.ThrowHelper.ThrowArgumentOutOfRangeException(System.ExceptionArgument.value);
_value = value;
}
private Rune(uint scalarValue, bool _)
{
_value = scalarValue;
}
public static bool operator ==(System.Text.Rune left, System.Text.Rune right)
{
return left._value == right._value;
}
public static bool operator !=(System.Text.Rune left, System.Text.Rune right)
{
return left._value != right._value;
}
public static bool operator <(System.Text.Rune left, System.Text.Rune right)
{
return left._value < right._value;
}
public static bool operator <=(System.Text.Rune left, System.Text.Rune right)
{
return left._value <= right._value;
}
public static bool operator >(System.Text.Rune left, System.Text.Rune right)
{
return left._value > right._value;
}
public static bool operator >=(System.Text.Rune left, System.Text.Rune right)
{
return left._value >= right._value;
}
public static explicit operator System.Text.Rune(char ch)
{
return new System.Text.Rune(ch);
}
public static explicit operator System.Text.Rune(uint value)
{
return new System.Text.Rune(value);
}
public static explicit operator System.Text.Rune(int value)
{
return new System.Text.Rune(value);
}
private unsafe static System.Text.Rune ChangeCaseCultureAware(System.Text.Rune rune, CultureInfo culture, bool toUpper)
{
Span<char> span = new Span<char>(stackalloc byte[4], 2);
Span<char> destination = new Span<char>(stackalloc byte[4], 2);
int length = rune.EncodeToUtf16(span);
span = span.Slice(0, length);
destination = destination.Slice(0, length);
if (toUpper)
span.ToUpper(destination, culture);
else
span.ToLower(destination, culture);
if (rune.IsBmp)
return UnsafeCreate(destination[0]);
return UnsafeCreate(System.Text.UnicodeUtility.GetScalarFromUtf16SurrogatePair(destination[0], destination[1]));
}
public int CompareTo(System.Text.Rune other)
{
return Value - other.Value;
}
public static OperationStatus DecodeFromUtf16(ReadOnlySpan<char> source, out System.Text.Rune result, out int charsConsumed)
{
if (!source.IsEmpty) {
char c = source[0];
if (TryCreate(c, out result)) {
charsConsumed = 1;
return OperationStatus.Done;
}
if (source.Length > 1) {
char lowSurrogate = source[1];
if (TryCreate(c, lowSurrogate, out result)) {
charsConsumed = 2;
return OperationStatus.Done;
}
} else if (char.IsHighSurrogate(c)) {
goto IL_004c;
}
charsConsumed = 1;
result = ReplacementChar;
return OperationStatus.InvalidData;
}
goto IL_004c;
IL_004c:
charsConsumed = source.Length;
result = ReplacementChar;
return OperationStatus.NeedMoreData;
}
public static OperationStatus DecodeFromUtf8(ReadOnlySpan<byte> source, out System.Text.Rune result, out int bytesConsumed)
{
int num = 0;
if (source.IsEmpty)
goto IL_0153;
uint num2 = source[0];
if (System.Text.UnicodeUtility.IsAsciiCodePoint(num2)) {
bytesConsumed = 1;
result = UnsafeCreate(num2);
return OperationStatus.Done;
}
num = 1;
if (System.Text.UnicodeUtility.IsInRangeInclusive(num2, 194, 244)) {
num2 = num2 - 194 << 6;
if (source.Length <= 1)
goto IL_0153;
int num3 = (sbyte)source[1];
if (num3 < -64) {
num2 = (uint)((int)num2 + num3);
num2 += 128;
num2 += 128;
if (num2 < 2048)
goto IL_0140;
if (System.Text.UnicodeUtility.IsInRangeInclusive(num2, 2080, 3343) && !System.Text.UnicodeUtility.IsInRangeInclusive(num2, 2912, 2943) && !System.Text.UnicodeUtility.IsInRangeInclusive(num2, 3072, 3087)) {
num = 2;
if (source.Length <= 2)
goto IL_0153;
num3 = (sbyte)source[2];
if (num3 < -64) {
num2 <<= 6;
num2 = (uint)((int)num2 + num3);
num2 += 128;
num2 -= 131072;
if (num2 > 65535) {
num = 3;
if (source.Length <= 3)
goto IL_0153;
num3 = (sbyte)source[3];
if (num3 >= -64)
goto IL_0163;
num2 <<= 6;
num2 = (uint)((int)num2 + num3);
num2 += 128;
num2 -= 4194304;
}
goto IL_0140;
}
}
}
}
goto IL_0163;
IL_0140:
bytesConsumed = num + 1;
result = UnsafeCreate(num2);
return OperationStatus.Done;
IL_0163:
bytesConsumed = num;
result = ReplacementChar;
return OperationStatus.InvalidData;
IL_0153:
bytesConsumed = num;
result = ReplacementChar;
return OperationStatus.NeedMoreData;
}
public static OperationStatus DecodeLastFromUtf16(ReadOnlySpan<char> source, out System.Text.Rune result, out int charsConsumed)
{
int num = source.Length - 1;
if ((uint)num < (uint)source.Length) {
char c = source[num];
if (TryCreate(c, out result)) {
charsConsumed = 1;
return OperationStatus.Done;
}
if (char.IsLowSurrogate(c)) {
num--;
if ((uint)num < (uint)source.Length && TryCreate(source[num], c, out result)) {
charsConsumed = 2;
return OperationStatus.Done;
}
charsConsumed = 1;
result = ReplacementChar;
return OperationStatus.InvalidData;
}
}
charsConsumed = (int)((uint)(-source.Length) >> 31);
result = ReplacementChar;
return OperationStatus.NeedMoreData;
}
public static OperationStatus DecodeLastFromUtf8(ReadOnlySpan<byte> source, out System.Text.Rune value, out int bytesConsumed)
{
int num = source.Length - 1;
if ((uint)num < (uint)source.Length) {
uint num2 = source[num];
if (System.Text.UnicodeUtility.IsAsciiCodePoint(num2)) {
bytesConsumed = 1;
value = UnsafeCreate(num2);
return OperationStatus.Done;
}
if (((byte)num2 & 64) != 0)
return DecodeFromUtf8(source.Slice(num), out value, out bytesConsumed);
int num3 = 3;
OperationStatus result2;
System.Text.Rune result;
int bytesConsumed2;
while (true) {
if (num3 > 0) {
num--;
if ((uint)num < (uint)source.Length) {
if ((sbyte)source[num] < -64) {
num3--;
continue;
}
source = source.Slice(num);
result2 = DecodeFromUtf8(source, out result, out bytesConsumed2);
if (bytesConsumed2 == source.Length)
break;
}
}
value = ReplacementChar;
bytesConsumed = 1;
return OperationStatus.InvalidData;
}
bytesConsumed = bytesConsumed2;
value = result;
return result2;
}
value = ReplacementChar;
bytesConsumed = 0;
return OperationStatus.NeedMoreData;
}
public int EncodeToUtf16(Span<char> destination)
{
if (!TryEncodeToUtf16(destination, out int charsWritten))
System.ThrowHelper.ThrowArgumentException_DestinationTooShort();
return charsWritten;
}
public int EncodeToUtf8(Span<byte> destination)
{
if (!TryEncodeToUtf8(destination, out int bytesWritten))
System.ThrowHelper.ThrowArgumentException_DestinationTooShort();
return bytesWritten;
}
public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object obj)
{
if (obj is System.Text.Rune) {
System.Text.Rune other = (System.Text.Rune)obj;
return Equals(other);
}
return false;
}
public bool Equals(System.Text.Rune other)
{
return this == other;
}
public override int GetHashCode()
{
return Value;
}
public static System.Text.Rune GetRuneAt(string input, int index)
{
int num = ReadRuneFromString(input, index);
if (num < 0)
System.ThrowHelper.ThrowArgumentException_CannotExtractScalar(System.ExceptionArgument.index);
return UnsafeCreate((uint)num);
}
public static bool IsValid(int value)
{
return IsValid((uint)value);
}
[CLSCompliant(false)]
public static bool IsValid(uint value)
{
return System.Text.UnicodeUtility.IsValidUnicodeScalar(value);
}
internal static int ReadFirstRuneFromUtf16Buffer(ReadOnlySpan<char> input)
{
if (input.IsEmpty)
return -1;
uint num = input[0];
if (System.Text.UnicodeUtility.IsSurrogateCodePoint(num)) {
if (!System.Text.UnicodeUtility.IsHighSurrogateCodePoint(num))
return -1;
if (input.Length <= 1)
return -1;
uint num2 = input[1];
if (!System.Text.UnicodeUtility.IsLowSurrogateCodePoint(num2))
return -1;
num = System.Text.UnicodeUtility.GetScalarFromUtf16SurrogatePair(num, num2);
}
return (int)num;
}
private static int ReadRuneFromString(string input, int index)
{
if (input == null)
System.ThrowHelper.ThrowArgumentNullException(System.ExceptionArgument.input);
if ((uint)index >= (uint)input.Length)
System.ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException();
uint num = input[index];
if (System.Text.UnicodeUtility.IsSurrogateCodePoint(num)) {
if (!System.Text.UnicodeUtility.IsHighSurrogateCodePoint(num))
return -1;
index++;
if ((uint)index >= (uint)input.Length)
return -1;
uint num2 = input[index];
if (!System.Text.UnicodeUtility.IsLowSurrogateCodePoint(num2))
return -1;
num = System.Text.UnicodeUtility.GetScalarFromUtf16SurrogatePair(num, num2);
}
return (int)num;
}
public unsafe override string ToString()
{
if (IsBmp)
return ((char)(ushort)_value).ToString();
Span<char> span = new Span<char>(stackalloc byte[4], 2);
System.Text.UnicodeUtility.GetUtf16SurrogatesFromSupplementaryPlaneScalar(_value, out span[0], out span[1]);
return span.ToString();
}
public static bool TryCreate(char ch, out System.Text.Rune result)
{
if (!System.Text.UnicodeUtility.IsSurrogateCodePoint(ch)) {
result = UnsafeCreate(ch);
return true;
}
result = default(System.Text.Rune);
return false;
}
public static bool TryCreate(char highSurrogate, char lowSurrogate, out System.Text.Rune result)
{
uint num = (uint)(highSurrogate - 55296);
uint num2 = (uint)(lowSurrogate - 56320);
if ((num | num2) <= 1023) {
result = UnsafeCreate((uint)((int)(num << 10) + (lowSurrogate - 56320) + 65536));
return true;
}
result = default(System.Text.Rune);
return false;
}
public static bool TryCreate(int value, out System.Text.Rune result)
{
return TryCreate((uint)value, out result);
}
[CLSCompliant(false)]
public static bool TryCreate(uint value, out System.Text.Rune result)
{
if (System.Text.UnicodeUtility.IsValidUnicodeScalar(value)) {
result = UnsafeCreate(value);
return true;
}
result = default(System.Text.Rune);
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryEncodeToUtf16(Span<char> destination, out int charsWritten)
{
return TryEncodeToUtf16(this, destination, out charsWritten);
}
private static bool TryEncodeToUtf16(System.Text.Rune value, Span<char> destination, out int charsWritten)
{
if (!destination.IsEmpty) {
if (value.IsBmp) {
destination[0] = (char)value._value;
charsWritten = 1;
return true;
}
if (destination.Length > 1) {
System.Text.UnicodeUtility.GetUtf16SurrogatesFromSupplementaryPlaneScalar(value._value, out destination[0], out destination[1]);
charsWritten = 2;
return true;
}
}
charsWritten = 0;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryEncodeToUtf8(Span<byte> destination, out int bytesWritten)
{
return TryEncodeToUtf8(this, destination, out bytesWritten);
}
private static bool TryEncodeToUtf8(System.Text.Rune value, Span<byte> destination, out int bytesWritten)
{
if (!destination.IsEmpty) {
if (value.IsAscii) {
destination[0] = (byte)value._value;
bytesWritten = 1;
return true;
}
if (destination.Length > 1) {
if ((long)value.Value <= 2047) {
destination[0] = (byte)(value._value + 12288 >> 6);
destination[1] = (byte)((value._value & 63) + 128);
bytesWritten = 2;
return true;
}
if (destination.Length > 2) {
if ((long)value.Value <= 65535) {
destination[0] = (byte)(value._value + 917504 >> 12);
destination[1] = (byte)(((value._value & 4032) >> 6) + 128);
destination[2] = (byte)((value._value & 63) + 128);
bytesWritten = 3;
return true;
}
if (destination.Length > 3) {
destination[0] = (byte)(value._value + 62914560 >> 18);
destination[1] = (byte)(((value._value & 258048) >> 12) + 128);
destination[2] = (byte)(((value._value & 4032) >> 6) + 128);
destination[3] = (byte)((value._value & 63) + 128);
bytesWritten = 4;
return true;
}
}
}
}
bytesWritten = 0;
return false;
}
public static bool TryGetRuneAt(string input, int index, out System.Text.Rune value)
{
int num = ReadRuneFromString(input, index);
if (num >= 0) {
value = UnsafeCreate((uint)num);
return true;
}
value = default(System.Text.Rune);
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static System.Text.Rune UnsafeCreate(uint scalarValue)
{
return new System.Text.Rune(scalarValue, false);
}
public static double GetNumericValue(System.Text.Rune value)
{
if (value.IsAscii) {
uint num = value._value - 48;
if (num > 9)
return -1;
return (double)num;
}
if (value.IsBmp)
return CharUnicodeInfo.GetNumericValue((char)value._value);
return CharUnicodeInfo.GetNumericValue(value.ToString(), 0);
}
public static UnicodeCategory GetUnicodeCategory(System.Text.Rune value)
{
if (value.IsAscii)
return (UnicodeCategory)(AsciiCharInfo[value.Value] & 31);
return GetUnicodeCategoryNonAscii(value);
}
private static UnicodeCategory GetUnicodeCategoryNonAscii(System.Text.Rune value)
{
if (value.IsBmp)
return CharUnicodeInfo.GetUnicodeCategory((char)value._value);
return CharUnicodeInfo.GetUnicodeCategory(value.ToString(), 0);
}
private static bool IsCategoryLetter(UnicodeCategory category)
{
return System.Text.UnicodeUtility.IsInRangeInclusive((uint)category, 0, 4);
}
private static bool IsCategoryLetterOrDecimalDigit(UnicodeCategory category)
{
if (!System.Text.UnicodeUtility.IsInRangeInclusive((uint)category, 0, 4))
return category == UnicodeCategory.DecimalDigitNumber;
return true;
}
private static bool IsCategoryNumber(UnicodeCategory category)
{
return System.Text.UnicodeUtility.IsInRangeInclusive((uint)category, 8, 10);
}
private static bool IsCategoryPunctuation(UnicodeCategory category)
{
return System.Text.UnicodeUtility.IsInRangeInclusive((uint)category, 18, 24);
}
private static bool IsCategorySeparator(UnicodeCategory category)
{
return System.Text.UnicodeUtility.IsInRangeInclusive((uint)category, 11, 13);
}
private static bool IsCategorySymbol(UnicodeCategory category)
{
return System.Text.UnicodeUtility.IsInRangeInclusive((uint)category, 25, 28);
}
public static bool IsControl(System.Text.Rune value)
{
return (uint)((int)(value._value + 1) & -129) <= 32;
}
public static bool IsDigit(System.Text.Rune value)
{
if (value.IsAscii)
return System.Text.UnicodeUtility.IsInRangeInclusive(value._value, 48, 57);
return GetUnicodeCategoryNonAscii(value) == UnicodeCategory.DecimalDigitNumber;
}
public static bool IsLetter(System.Text.Rune value)
{
if (value.IsAscii)
return (uint)((int)(value._value - 65) & -33) <= 25;
return IsCategoryLetter(GetUnicodeCategoryNonAscii(value));
}
public static bool IsLetterOrDigit(System.Text.Rune value)
{
if (value.IsAscii)
return (AsciiCharInfo[value.Value] & 64) != 0;
return IsCategoryLetterOrDecimalDigit(GetUnicodeCategoryNonAscii(value));
}
public static bool IsLower(System.Text.Rune value)
{
if (value.IsAscii)
return System.Text.UnicodeUtility.IsInRangeInclusive(value._value, 97, 122);
return GetUnicodeCategoryNonAscii(value) == UnicodeCategory.LowercaseLetter;
}
public static bool IsNumber(System.Text.Rune value)
{
if (value.IsAscii)
return System.Text.UnicodeUtility.IsInRangeInclusive(value._value, 48, 57);
return IsCategoryNumber(GetUnicodeCategoryNonAscii(value));
}
public static bool IsPunctuation(System.Text.Rune value)
{
return IsCategoryPunctuation(GetUnicodeCategory(value));
}
public static bool IsSeparator(System.Text.Rune value)
{
return IsCategorySeparator(GetUnicodeCategory(value));
}
public static bool IsSymbol(System.Text.Rune value)
{
return IsCategorySymbol(GetUnicodeCategory(value));
}
public static bool IsUpper(System.Text.Rune value)
{
if (value.IsAscii)
return System.Text.UnicodeUtility.IsInRangeInclusive(value._value, 65, 90);
return GetUnicodeCategoryNonAscii(value) == UnicodeCategory.UppercaseLetter;
}
public static bool IsWhiteSpace(System.Text.Rune value)
{
if (value.IsAscii)
return (AsciiCharInfo[value.Value] & 128) != 0;
if (value.IsBmp)
return char.IsWhiteSpace((char)value._value);
return false;
}
public static System.Text.Rune ToLower(System.Text.Rune value, CultureInfo culture)
{
if (culture == null)
System.ThrowHelper.ThrowArgumentNullException(System.ExceptionArgument.culture);
return ChangeCaseCultureAware(value, culture, false);
}
public static System.Text.Rune ToLowerInvariant(System.Text.Rune value)
{
if (value.IsAscii)
return UnsafeCreate(System.Text.Unicode.Utf16Utility.ConvertAllAsciiCharsInUInt32ToLowercase(value._value));
return ChangeCaseCultureAware(value, CultureInfo.InvariantCulture, false);
}
public static System.Text.Rune ToUpper(System.Text.Rune value, CultureInfo culture)
{
if (culture == null)
System.ThrowHelper.ThrowArgumentNullException(System.ExceptionArgument.culture);
return ChangeCaseCultureAware(value, culture, true);
}
public static System.Text.Rune ToUpperInvariant(System.Text.Rune value)
{
if (value.IsAscii)
return UnsafeCreate(System.Text.Unicode.Utf16Utility.ConvertAllAsciiCharsInUInt32ToUppercase(value._value));
return ChangeCaseCultureAware(value, CultureInfo.InvariantCulture, true);
}
int IComparable.CompareTo(object obj)
{
if (obj == null)
return 1;
if (obj is System.Text.Rune) {
System.Text.Rune other = (System.Text.Rune)obj;
return CompareTo(other);
}
throw new ArgumentException(System.SR.Arg_MustBeRune);
}
}
}